Added MusicAsset, renamed SoundAsset to be SoundEffectAsset. Created new directories for fx and music types of sound. (#105)

This commit is contained in:
Bas Antonius de Jong
2025-10-02 00:08:39 +02:00
committed by GitHub
parent 2003fcbf5b
commit fb0e16acc2
19 changed files with 142 additions and 36 deletions

View File

@@ -10,10 +10,12 @@ import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.toop.framework.asset.AssetManager; import org.toop.framework.asset.AssetManager;
import org.toop.framework.asset.resources.LocalizationAsset; import org.toop.framework.asset.resources.LocalizationAsset;
import org.toop.framework.audio.SoundManager;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import java.util.Locale; import java.util.Locale;
import java.util.ResourceBundle;
public class App extends Application { public class App extends Application {
private static Stage stage; private static Stage stage;
@@ -49,6 +51,9 @@ public class App extends Application {
App.stage = stage; App.stage = stage;
App.scene = scene; App.scene = scene;
App.root = root; App.root = root;
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).postEvent();
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(0.3)).postEvent();
} }
public static void activate(Menu menu) { public static void activate(Menu menu) {

Binary file not shown.

View File

@@ -102,6 +102,14 @@
<version>25</version> <version>25</version>
</dependency> </dependency>
<!-- JavaFX Media -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections --> <!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency> <dependency>
<groupId>org.reflections</groupId> <groupId>org.reflections</groupId>
@@ -122,24 +130,6 @@
<target>25</target> <target>25</target>
<release>25</release> <release>25</release>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<!-- <compilerArgs>-->
<!-- <arg>-XDcompilePolicy=simple</arg>-->
<!-- <arg>&#45;&#45;should-stop=ifError=FLOW</arg>-->
<!-- <arg>-Xplugin:ErrorProne</arg>-->
<!-- </compilerArgs>-->
<!-- <annotationProcessorPaths>-->
<!-- <path>-->
<!-- <groupId>com.google.errorprone</groupId>-->
<!-- <artifactId>error_prone_core</artifactId>-->
<!-- <version>2.42.0</version>-->
<!-- </path>-->
<!-- &lt;!&ndash; Other annotation processors go here.-->
<!-- If 'annotationProcessorPaths' is set, processors will no longer be-->
<!-- discovered on the regular -classpath; see also 'Using Error Prone-->
<!-- together with other annotation processors' below. &ndash;&gt;-->
<!-- </annotationProcessorPaths>-->
<!-- <fork>true</fork>-->
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>

View File

@@ -242,4 +242,4 @@ public class AssetLoader {
int i = name.lastIndexOf('.'); int i = name.lastIndexOf('.');
return (i > 0) ? name.substring(i + 1) : ""; return (i > 0) ? name.substring(i + 1) : "";
} }
} }

View File

@@ -147,4 +147,4 @@ public class AssetManager {
public static void addAsset(Asset<? extends BaseResource> asset) { public static void addAsset(Asset<? extends BaseResource> asset) {
assets.put(asset.getName(), asset); assets.put(asset.getName(), asset);
} }
} }

View File

@@ -0,0 +1,38 @@
package org.toop.framework.asset.resources;
import javafx.scene.media.Media;
import java.io.*;
@FileExtension({"mp3"})
public class MusicAsset extends BaseResource implements LoadableResource {
private Media media;
public MusicAsset(final File audioFile) {
super(audioFile);
}
public Media getMedia() {
if (media == null) {
media = new Media(file.toURI().toString());
}
return media;
}
@Override
public void load() {
if (media == null) media = new Media(file.toURI().toString());
this.isLoaded = true;
}
@Override
public void unload() {
media = null;
isLoaded = false;
}
@Override
public boolean isLoaded() {
return isLoaded;
}
}

View File

@@ -1,12 +1,15 @@
package org.toop.framework.asset.resources; package org.toop.framework.asset.resources;
import javafx.scene.media.Media;
import javax.sound.sampled.*; import javax.sound.sampled.*;
import java.io.*; import java.io.*;
import java.net.URI;
@FileExtension({"wav"}) @FileExtension({"wav"})
public class AudioAsset extends BaseResource implements LoadableResource { public class SoundEffectAsset extends BaseResource implements LoadableResource {
public AudioAsset(final File audioFile) { public SoundEffectAsset(final File audioFile) {
super(audioFile); super(audioFile);
} }

View File

@@ -1,9 +1,12 @@
package org.toop.framework.audio; package org.toop.framework.audio;
import javafx.application.Platform;
import javafx.scene.media.MediaPlayer;
import org.toop.framework.SnowflakeGenerator; import org.toop.framework.SnowflakeGenerator;
import org.toop.framework.asset.Asset; import org.toop.framework.asset.Asset;
import org.toop.framework.asset.AssetManager; import org.toop.framework.asset.AssetManager;
import org.toop.framework.asset.resources.AudioAsset; import org.toop.framework.asset.resources.MusicAsset;
import org.toop.framework.asset.resources.SoundEffectAsset;
import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
@@ -12,13 +15,17 @@ import java.util.*;
import javax.sound.sampled.*; import javax.sound.sampled.*;
public class SoundManager { public class SoundManager {
private final Map<Long, Clip> activeClips = new HashMap<>(); private final List<MediaPlayer> activeMusic = new ArrayList<>();
private final HashMap<String, AudioAsset> audioResources = new HashMap<>(); private final Queue<MusicAsset> backgroundMusicQueue = new LinkedList<>();
private final Map<Long, Clip> activeSoundEffects = new HashMap<>();
private final HashMap<String, SoundEffectAsset> audioResources = new HashMap<>();
private final SnowflakeGenerator idGenerator = new SnowflakeGenerator(); // TODO: Don't create a new generator private final SnowflakeGenerator idGenerator = new SnowflakeGenerator(); // TODO: Don't create a new generator
private double volume = 1.0;
public SoundManager() { public SoundManager() {
// Get all Audio Resources and add them to a list. // Get all Audio Resources and add them to a list.
for (Asset<AudioAsset> asset : AssetManager.getAllOfType(AudioAsset.class)) { for (Asset<SoundEffectAsset> asset : AssetManager.getAllOfType(SoundEffectAsset.class)) {
try { try {
this.addAudioResource(asset); this.addAudioResource(asset);
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) { } catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) {
@@ -27,7 +34,9 @@ public class SoundManager {
} }
new EventFlow() new EventFlow()
.listen(this::handlePlaySound) .listen(this::handlePlaySound)
.listen(this::handleStopSound); .listen(this::handleStopSound)
.listen(this::handleMusicStart)
.listen(this::handleVolumeChange);
} }
private void handlePlaySound(AudioEvents.PlayAudio event) { private void handlePlaySound(AudioEvents.PlayAudio event) {
@@ -42,14 +51,70 @@ public class SoundManager {
this.stopSound(event.clipId()); this.stopSound(event.clipId());
} }
private void addAudioResource(Asset<AudioAsset> audioAsset) private void addAudioResource(Asset<SoundEffectAsset> audioAsset)
throws IOException, UnsupportedAudioFileException, LineUnavailableException { throws IOException, UnsupportedAudioFileException, LineUnavailableException {
this.audioResources.put(audioAsset.getName(), audioAsset.getResource()); this.audioResources.put(audioAsset.getName(), audioAsset.getResource());
} }
private void handleVolumeChange(AudioEvents.ChangeVolume event) {
if (event.newVolume() > 1.0) this.volume = 1.0;
else this.volume = Math.max(event.newVolume(), 0.0);
for (MediaPlayer mediaPlayer : this.activeMusic) {
mediaPlayer.setVolume(this.volume);
}
}
private void handleMusicStart(AudioEvents.StartBackgroundMusic e) {
backgroundMusicQueue.clear();
Platform.runLater(() -> {
backgroundMusicQueue.addAll(
AssetManager.getAllOfType(MusicAsset.class).stream()
.map(Asset::getResource)
.toList()
);
backgroundMusicPlayer();
});
}
private void addBackgroundMusic(MusicAsset musicAsset) {
backgroundMusicQueue.add(musicAsset);
}
private void backgroundMusicPlayer() {
MusicAsset ma = backgroundMusicQueue.poll();
if (ma == null) return;
MediaPlayer mediaPlayer = new MediaPlayer(ma.getMedia());
mediaPlayer.setOnEndOfMedia(() -> {
addBackgroundMusic(ma);
activeMusic.remove(mediaPlayer);
mediaPlayer.dispose();
ma.unload();
backgroundMusicPlayer(); // play next
});
mediaPlayer.setOnStopped(() -> {
addBackgroundMusic(ma);
activeMusic.remove(mediaPlayer);
ma.unload();
});
mediaPlayer.setOnError(() -> {
addBackgroundMusic(ma);
activeMusic.remove(mediaPlayer);
ma.unload();
});
mediaPlayer.setVolume(this.volume);
mediaPlayer.play();
activeMusic.add(mediaPlayer);
}
private long playSound(String audioFileName, boolean loop) throws UnsupportedAudioFileException, LineUnavailableException, IOException { private long playSound(String audioFileName, boolean loop) throws UnsupportedAudioFileException, LineUnavailableException, IOException {
AudioAsset asset = audioResources.get(audioFileName); SoundEffectAsset asset = audioResources.get(audioFileName);
// Return -1 which indicates resource wasn't available // Return -1 which indicates resource wasn't available
if (asset == null){ if (asset == null){
@@ -71,12 +136,12 @@ public class SoundManager {
long clipId = idGenerator.nextId(); long clipId = idGenerator.nextId();
// store it so we can stop it later // store it so we can stop it later
activeClips.put(clipId, clip); // TODO: Do on snowflake for specific sound to stop activeSoundEffects.put(clipId, clip); // TODO: Do on snowflake for specific sound to stop
// remove when finished (only for non-looping sounds) // remove when finished (only for non-looping sounds)
clip.addLineListener(event -> { clip.addLineListener(event -> {
if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) { if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) {
activeClips.remove(clipId); activeSoundEffects.remove(clipId);
clip.close(); clip.close();
} }
}); });
@@ -86,7 +151,7 @@ public class SoundManager {
} }
public void stopSound(long clipId) { public void stopSound(long clipId) {
Clip clip = activeClips.get(clipId); Clip clip = activeSoundEffects.get(clipId);
if (clip == null) { if (clip == null) {
return; return;
@@ -94,14 +159,14 @@ public class SoundManager {
clip.stop(); clip.stop();
clip.close(); clip.close();
activeClips.remove(clipId); activeSoundEffects.remove(clipId);
} }
public void stopAllSounds() { public void stopAllSounds() {
for (Clip clip : activeClips.values()) { for (Clip clip : activeSoundEffects.values()) {
clip.stop(); clip.stop();
clip.close(); clip.close();
} }
activeClips.clear(); activeSoundEffects.clear();
} }
} }

View File

@@ -1,5 +1,6 @@
package org.toop.framework.audio.events; package org.toop.framework.audio.events;
import org.toop.framework.asset.resources.MusicAsset;
import org.toop.framework.eventbus.events.EventWithoutSnowflake; import org.toop.framework.eventbus.events.EventWithoutSnowflake;
import org.toop.framework.eventbus.events.EventsBase; import org.toop.framework.eventbus.events.EventsBase;
@@ -9,4 +10,8 @@ public class AudioEvents extends EventsBase {
implements EventWithoutSnowflake {} implements EventWithoutSnowflake {}
public record StopAudio(long clipId) implements EventWithoutSnowflake {} public record StopAudio(long clipId) implements EventWithoutSnowflake {}
public record StartBackgroundMusic() implements EventWithoutSnowflake {}
public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {}
} }