Reworked to now use better defined generics and easier to use API. Added AudioResource to be used in changing volume

This commit is contained in:
lieght
2025-10-11 06:09:13 +02:00
parent 123ecc7d3a
commit b101734fd7
13 changed files with 119 additions and 95 deletions

View File

@@ -6,15 +6,11 @@ import org.toop.framework.audio.AudioEventListener;
import org.toop.framework.audio.AudioVolumeManager; import org.toop.framework.audio.AudioVolumeManager;
import org.toop.framework.audio.MusicManager; import org.toop.framework.audio.MusicManager;
import org.toop.framework.audio.SoundEffectManager; import org.toop.framework.audio.SoundEffectManager;
import org.toop.framework.audio.interfaces.AudioManager;
import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.networking.NetworkingClientManager; import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.networking.NetworkingInitializationException;
import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceLoader;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
import javax.sound.sampled.Clip;
public final class Main { public final class Main {
static void main(String[] args) { static void main(String[] args) {
initSystems(); initSystems();
@@ -25,8 +21,8 @@ public final class Main {
ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"));
new Thread(NetworkingClientManager::new).start(); new Thread(NetworkingClientManager::new).start();
new Thread(() -> { new Thread(() -> {
AudioEventListener a = AudioEventListener<?, ?> a =
new AudioEventListener( new AudioEventListener<>(
new MusicManager(), new MusicManager(),
new SoundEffectManager(), new SoundEffectManager(),
new AudioVolumeManager() new AudioVolumeManager()

View File

@@ -5,15 +5,16 @@ import org.toop.framework.audio.interfaces.MusicManager;
import org.toop.framework.audio.interfaces.SoundEffectManager; import org.toop.framework.audio.interfaces.SoundEffectManager;
import org.toop.framework.audio.interfaces.VolumeManager; import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.resource.types.AudioResource;
public class AudioEventListener { public class AudioEventListener<T extends AudioResource, K extends AudioResource> {
private final MusicManager<?> musicManager; private final MusicManager<T> musicManager;
private final SoundEffectManager<?> soundEffectManager; private final SoundEffectManager<K> soundEffectManager;
private final VolumeManager audioVolumeManager; private final VolumeManager audioVolumeManager;
public AudioEventListener( public AudioEventListener(
MusicManager<?> musicManager, MusicManager<T> musicManager,
SoundEffectManager<?> soundEffectManager, SoundEffectManager<K> soundEffectManager,
VolumeManager audioVolumeManager VolumeManager audioVolumeManager
) { ) {
this.musicManager = musicManager; this.musicManager = musicManager;

View File

@@ -1,10 +1,8 @@
package org.toop.framework.audio; package org.toop.framework.audio;
import javafx.scene.media.MediaPlayer;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import org.toop.framework.audio.interfaces.AudioManager; import org.toop.framework.audio.interfaces.AudioManager;
import org.toop.framework.audio.interfaces.VolumeManager; import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.resource.types.AudioResource;
public class AudioVolumeManager implements VolumeManager { public class AudioVolumeManager implements VolumeManager {
private double volume = 0.0; private double volume = 0.0;
@@ -13,54 +11,39 @@ public class AudioVolumeManager implements VolumeManager {
public AudioVolumeManager() {} public AudioVolumeManager() {}
private <T extends MediaPlayer> void updateMusicVolume(T mediaPlayer) { private <T extends AudioResource> void updateVolume(T resource, double level) {
mediaPlayer.setVolume(this.musicVolume * this.volume); resource.updateVolume(level);
}
private <T extends Clip> void updateSoundEffectVolume(T clip) {
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volumeControl =
(FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float min = volumeControl.getMinimum();
float max = volumeControl.getMaximum();
float dB =
(float)
(Math.log10(Math.max(this.fxVolume * this.volume, 0.0001))
* 20.0); // convert linear to dB
dB = Math.max(min, Math.min(max, dB));
volumeControl.setValue(dB);
}
} }
private double limitVolume(double volume) { private double limitVolume(double volume) {
if (volume > 1.0) return 1.0; return Math.min(1.0, Math.max(0.0, volume / 100));
else return Math.max(volume, 0.0);
} }
@Override @Override
public void setVolume(double newVolume, AudioManager<?> sm, AudioManager<?> mm) { public <T extends AudioResource, K extends AudioResource> void setVolume(
this.volume = limitVolume(newVolume / 100); double newVolume, AudioManager<T> sm, AudioManager<K> mm) {
for (var clip : sm.getActiveAudio()) { this.volume = limitVolume(newVolume);
this.updateSoundEffectVolume((Clip) clip); for (T clip : sm.getActiveAudio()) {
this.updateVolume(clip, fxVolume * volume);
} }
for (var mediaPlayer : mm.getActiveAudio()) { for (K mediaPlayer : mm.getActiveAudio()) {
this.updateMusicVolume((MediaPlayer) mediaPlayer); this.updateVolume(mediaPlayer, musicVolume * volume);
} }
} }
@Override @Override
public void setFxVolume(double newVolume, AudioManager<?> sm) { public <T extends AudioResource> void setFxVolume(double newVolume, AudioManager<T> sm) {
this.fxVolume = limitVolume(newVolume / 100); this.fxVolume = limitVolume(newVolume);
for (var clip : sm.getActiveAudio()) { for (T clip : sm.getActiveAudio()) {
this.updateSoundEffectVolume((Clip) clip); // TODO: What if not clip this.updateVolume(clip, fxVolume * volume);
} }
} }
@Override @Override
public void setMusicVolume(double newVolume, AudioManager<?> mm) { public <T extends AudioResource> void setMusicVolume(double newVolume, AudioManager<T> mm) {
this.musicVolume = limitVolume(newVolume / 100); this.musicVolume = limitVolume(newVolume);
for (var mediaPlayer : mm.getActiveAudio()) { for (T mediaPlayer : mm.getActiveAudio()) {
this.updateMusicVolume((MediaPlayer) mediaPlayer); // TODO; What if not MediaPlayer this.updateVolume(mediaPlayer, musicVolume * volume);
} }
} }

View File

@@ -1,7 +1,5 @@
package org.toop.framework.audio; package org.toop.framework.audio;
import javafx.scene.media.MediaPlayer;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
@@ -9,35 +7,34 @@ import org.toop.framework.resource.resources.MusicAsset;
import java.util.*; import java.util.*;
public class MusicManager implements org.toop.framework.audio.interfaces.MusicManager<MediaPlayer> { public class MusicManager implements org.toop.framework.audio.interfaces.MusicManager<MusicAsset> {
private static final Logger logger = LogManager.getLogger(MusicManager.class); private static final Logger logger = LogManager.getLogger(MusicManager.class);
// private final List<MusicAsset> musicAssets = new ArrayList<>(); // TODO private final List<MusicAsset> backgroundMusic = new LinkedList<>();
private final List<MediaPlayer> backgroundMusic = new LinkedList<>();
private int playingIndex = 0; private int playingIndex = 0;
private boolean playing = false; private boolean playing = false;
public MusicManager() {} public MusicManager() {}
@Override @Override
public Collection<MediaPlayer> getActiveAudio() { public Collection<MusicAsset> getActiveAudio() {
return backgroundMusic; return backgroundMusic;
} }
private void addBackgroundMusic(MusicAsset musicAsset) { private void addBackgroundMusic(MusicAsset musicAsset) {
backgroundMusic.add(new MediaPlayer(musicAsset.getMedia())); backgroundMusic.add(musicAsset);
} }
private void addBackgroundMusic(MediaPlayer mediaPlayer) { public void play() {
backgroundMusic.add(mediaPlayer); if (playing) {
} logger.warn("MusicManager is already playing.");
return;
public void play() { // TODO maybe remove VolumeManager from input }
backgroundMusic.clear(); backgroundMusic.clear();
List<MediaPlayer> shuffledArray = List<MusicAsset> shuffledArray =
new ArrayList<>( new ArrayList<>(
ResourceManager.getAllOfType(MusicAsset.class).stream() ResourceManager.getAllOfType(MusicAsset.class).stream()
.map(ma -> .map(ma ->
initMediaPlayer(new MediaPlayer(ma.getResource().getMedia()))) initMediaPlayer(ma.getResource()))
.toList()); .toList());
Collections.shuffle(shuffledArray); Collections.shuffle(shuffledArray);
backgroundMusic.addAll(shuffledArray); backgroundMusic.addAll(shuffledArray);
@@ -50,35 +47,35 @@ public class MusicManager implements org.toop.framework.audio.interfaces.MusicMa
playingIndex = 0; playingIndex = 0;
} }
MediaPlayer ma = backgroundMusic.get(playingIndex); MusicAsset ma = backgroundMusic.get(playingIndex);
if (ma == null) { if (ma == null) {
logger.error("Background music player is null. Queue: {}", logger.error("Background music player is null. Queue: {}",
backgroundMusic.stream().map(e -> e.getMedia().getSource())); backgroundMusic.stream().map(e -> e.getMediaPlayer().getMedia().getSource()));
return; return;
} }
logger.info("Background music player is playing: {}", ma.getMedia().getSource()); //TODO shorten to name logger.info("Background music player is playing: {}", ma.getMediaPlayer().getMedia().getSource()); //TODO shorten to name
ma.play(); ma.getMediaPlayer().play();
this.playing = true; this.playing = true;
} }
private MediaPlayer initMediaPlayer(MediaPlayer mediaPlayer) { private MusicAsset initMediaPlayer(MusicAsset ma) {
mediaPlayer.setOnEndOfMedia(mediaPlayer::stop); ma.getMediaPlayer().setOnEndOfMedia(() -> ma.getMediaPlayer().stop());
mediaPlayer.setOnError( () -> { ma.getMediaPlayer().setOnError( () -> {
logger.error("Error playing music: {}", mediaPlayer.getMedia().getSource()); logger.error("Error playing music: {}", ma.getMediaPlayer().getError()); // TODO
backgroundMusic.remove(mediaPlayer); backgroundMusic.remove(ma);
mediaPlayer.stop(); ma.getMediaPlayer().stop();
}); });
mediaPlayer.setOnStopped( () -> { ma.getMediaPlayer().setOnStopped( () -> {
mediaPlayer.stop(); ma.getMediaPlayer().stop();
playingIndex++; playingIndex++;
this.playing = false; this.playing = false;
backgroundMusicPlayer(); backgroundMusicPlayer();
}); });
return mediaPlayer; return ma;
} }
} }

View File

@@ -2,18 +2,17 @@ package org.toop.framework.audio;
import org.toop.framework.resource.resources.SoundEffectAsset; import org.toop.framework.resource.resources.SoundEffectAsset;
import javax.sound.sampled.Clip;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class SoundEffectManager implements org.toop.framework.audio.interfaces.SoundEffectManager<Clip> { public class SoundEffectManager implements org.toop.framework.audio.interfaces.SoundEffectManager<SoundEffectAsset> {
private final Map<Long, Clip> activeSoundEffects = new HashMap<>(); private final Map<Long, SoundEffectAsset> activeSoundEffects = new HashMap<>();
private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>(); private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>();
@Override
public Collection<Clip> getActiveAudio() { public Collection<SoundEffectAsset> getActiveAudio() {
return this.activeSoundEffects.values(); return this.audioResources.values();
} }
@Override @Override

View File

@@ -1,5 +1,7 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
public interface MusicManager<T> extends AudioManager<T> { import org.toop.framework.resource.types.AudioResource;
public interface MusicManager<T extends AudioResource> extends AudioManager<T> {
void play(); void play();
} }

View File

@@ -1,6 +1,9 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
public interface SoundEffectManager<T> extends AudioManager<T> { import org.toop.framework.resource.resources.SoundEffectAsset;
import org.toop.framework.resource.types.AudioResource;
public interface SoundEffectManager<T extends AudioResource> extends AudioManager<T> {
void play(String name, boolean loop); void play(String name, boolean loop);
void stop(long clipId); void stop(long clipId);
} }

View File

@@ -1,9 +1,11 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
import org.toop.framework.resource.types.AudioResource;
public interface VolumeManager { public interface VolumeManager {
void setVolume(double newVolume, AudioManager<?> sm, AudioManager<?> mm); <T extends AudioResource, K extends AudioResource> void setVolume(double newVolume, AudioManager<T> sm, AudioManager<K> mm);
void setFxVolume(double newVolume, AudioManager<?> sm); <T extends AudioResource> void setFxVolume(double newVolume, AudioManager<T> sm);
void setMusicVolume(double newVolume, AudioManager<?> mm); <T extends AudioResource> void setMusicVolume(double newVolume, AudioManager<T> mm);
double getVolume(); double getVolume();
double getFxVolume(); double getFxVolume();
double getMusicVolume(); double getMusicVolume();

View File

@@ -2,33 +2,39 @@ package org.toop.framework.resource.resources;
import java.io.*; import java.io.*;
import javafx.scene.media.Media; import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import org.toop.framework.resource.types.AudioResource;
import org.toop.framework.resource.types.FileExtension; import org.toop.framework.resource.types.FileExtension;
import org.toop.framework.resource.types.LoadableResource; import org.toop.framework.resource.types.LoadableResource;
@FileExtension({"mp3"}) @FileExtension({"mp3"})
public class MusicAsset extends BaseResource implements LoadableResource { public class MusicAsset extends BaseResource implements LoadableResource, AudioResource {
private Media media; private MediaPlayer mediaPlayer;
public MusicAsset(final File audioFile) { public MusicAsset(final File audioFile) {
super(audioFile); super(audioFile);
} }
public Media getMedia() { public MediaPlayer getMediaPlayer() {
if (media == null) { load();
media = new Media(file.toURI().toString()); return mediaPlayer;
}
return media;
} }
@Override @Override
public void load() { public void load() {
if (media == null) media = new Media(file.toURI().toString()); if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer(new Media(file.toURI().toString()));
}
this.isLoaded = true; this.isLoaded = true;
} }
@Override @Override
public void unload() { public void unload() {
media = null; if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.dispose();
mediaPlayer = null;
}
isLoaded = false; isLoaded = false;
} }
@@ -36,4 +42,11 @@ public class MusicAsset extends BaseResource implements LoadableResource {
public boolean isLoaded() { public boolean isLoaded() {
return isLoaded; return isLoaded;
} }
@Override
public void updateVolume(double volume) {
if (mediaPlayer != null) {
mediaPlayer.setVolume(volume);
}
}
} }

View File

@@ -3,12 +3,15 @@ package org.toop.framework.resource.resources;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import javax.sound.sampled.*; import javax.sound.sampled.*;
import org.toop.framework.resource.types.AudioResource;
import org.toop.framework.resource.types.FileExtension; import org.toop.framework.resource.types.FileExtension;
import org.toop.framework.resource.types.LoadableResource; import org.toop.framework.resource.types.LoadableResource;
@FileExtension({"wav"}) @FileExtension({"wav"})
public class SoundEffectAsset extends BaseResource implements LoadableResource { public class SoundEffectAsset extends BaseResource implements LoadableResource, AudioResource {
private byte[] rawData; private byte[] rawData;
private Clip clip = null;
public SoundEffectAsset(final File audioFile) { public SoundEffectAsset(final File audioFile) {
super(audioFile); super(audioFile);
@@ -27,6 +30,7 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
inputStream = downSampleAudio(inputStream, baseFormat); inputStream = downSampleAudio(inputStream, baseFormat);
clip.open( clip.open(
inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary. inputStream); // ^ Clip can only run 16 bit and lower, thus downsampling necessary.
this.clip = clip;
return clip; return clip;
} }
@@ -77,4 +81,22 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
public boolean isLoaded() { public boolean isLoaded() {
return this.isLoaded; return this.isLoaded;
} }
@Override
public void updateVolume(double volume) {
{
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volumeControl =
(FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float min = volumeControl.getMinimum();
float max = volumeControl.getMaximum();
float dB =
(float)
(Math.log10(Math.max(volume, 0.0001))
* 20.0); // convert linear to dB
dB = Math.max(min, Math.min(max, dB));
volumeControl.setValue(dB);
}
}
}
} }

View File

@@ -0,0 +1,6 @@
package org.toop.framework.resource.types;
public interface AudioResource {
void updateVolume(double volume);
// TODO play and stop
}