diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 931bd2c..051cdfc 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -10,10 +10,12 @@ import javafx.scene.Scene; import javafx.stage.Stage; import org.toop.framework.asset.AssetManager; 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 java.util.Locale; -import java.util.ResourceBundle; public class App extends Application { private static Stage stage; @@ -49,6 +51,9 @@ public class App extends Application { App.stage = stage; App.scene = scene; 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) { diff --git a/app/src/main/resources/assets/audio/dramatic.wav b/app/src/main/resources/assets/audio/fx/dramatic.wav similarity index 100% rename from app/src/main/resources/assets/audio/dramatic.wav rename to app/src/main/resources/assets/audio/fx/dramatic.wav diff --git a/app/src/main/resources/assets/audio/hitsound0.wav b/app/src/main/resources/assets/audio/fx/hitsound0.wav similarity index 100% rename from app/src/main/resources/assets/audio/hitsound0.wav rename to app/src/main/resources/assets/audio/fx/hitsound0.wav diff --git a/app/src/main/resources/assets/audio/hitsound1.wav b/app/src/main/resources/assets/audio/fx/hitsound1.wav similarity index 100% rename from app/src/main/resources/assets/audio/hitsound1.wav rename to app/src/main/resources/assets/audio/fx/hitsound1.wav diff --git a/app/src/main/resources/assets/audio/mainmenu.wav b/app/src/main/resources/assets/audio/fx/mainmenu.wav similarity index 100% rename from app/src/main/resources/assets/audio/mainmenu.wav rename to app/src/main/resources/assets/audio/fx/mainmenu.wav diff --git a/app/src/main/resources/assets/audio/sadtrombone.wav b/app/src/main/resources/assets/audio/fx/sadtrombone.wav similarity index 100% rename from app/src/main/resources/assets/audio/sadtrombone.wav rename to app/src/main/resources/assets/audio/fx/sadtrombone.wav diff --git a/app/src/main/resources/assets/audio/scawymusic.wav b/app/src/main/resources/assets/audio/fx/scawymusic.wav similarity index 100% rename from app/src/main/resources/assets/audio/scawymusic.wav rename to app/src/main/resources/assets/audio/fx/scawymusic.wav diff --git a/app/src/main/resources/assets/audio/suspensful.wav b/app/src/main/resources/assets/audio/fx/suspensful.wav similarity index 100% rename from app/src/main/resources/assets/audio/suspensful.wav rename to app/src/main/resources/assets/audio/fx/suspensful.wav diff --git a/app/src/main/resources/assets/audio/testsound.wav b/app/src/main/resources/assets/audio/fx/testsound.wav similarity index 100% rename from app/src/main/resources/assets/audio/testsound.wav rename to app/src/main/resources/assets/audio/fx/testsound.wav diff --git a/app/src/main/resources/assets/audio/winsound.wav b/app/src/main/resources/assets/audio/fx/winsound.wav similarity index 100% rename from app/src/main/resources/assets/audio/winsound.wav rename to app/src/main/resources/assets/audio/fx/winsound.wav diff --git a/app/src/main/resources/assets/audio/music/damned.mp3 b/app/src/main/resources/assets/audio/music/damned.mp3 new file mode 100644 index 0000000..4b0bfbc Binary files /dev/null and b/app/src/main/resources/assets/audio/music/damned.mp3 differ diff --git a/app/src/main/resources/assets/audio/music/extraction-point.mp3 b/app/src/main/resources/assets/audio/music/extraction-point.mp3 new file mode 100644 index 0000000..1fc6430 Binary files /dev/null and b/app/src/main/resources/assets/audio/music/extraction-point.mp3 differ diff --git a/framework/pom.xml b/framework/pom.xml index 5e84787..b32b25c 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -102,6 +102,14 @@ 25 + + + org.openjfx + javafx-media + 25 + + + org.reflections @@ -122,24 +130,6 @@ 25 25 UTF-8 - - - - - - - - - - - - - - - - - - diff --git a/framework/src/main/java/org/toop/framework/asset/AssetLoader.java b/framework/src/main/java/org/toop/framework/asset/AssetLoader.java index dfcd1ac..bec2c97 100644 --- a/framework/src/main/java/org/toop/framework/asset/AssetLoader.java +++ b/framework/src/main/java/org/toop/framework/asset/AssetLoader.java @@ -242,4 +242,4 @@ public class AssetLoader { int i = name.lastIndexOf('.'); return (i > 0) ? name.substring(i + 1) : ""; } -} +} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/asset/AssetManager.java b/framework/src/main/java/org/toop/framework/asset/AssetManager.java index 1630ae0..f10d507 100644 --- a/framework/src/main/java/org/toop/framework/asset/AssetManager.java +++ b/framework/src/main/java/org/toop/framework/asset/AssetManager.java @@ -147,4 +147,4 @@ public class AssetManager { public static void addAsset(Asset asset) { assets.put(asset.getName(), asset); } -} +} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/asset/resources/MusicAsset.java b/framework/src/main/java/org/toop/framework/asset/resources/MusicAsset.java new file mode 100644 index 0000000..87c27f6 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/asset/resources/MusicAsset.java @@ -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; + } +} \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/asset/resources/AudioAsset.java b/framework/src/main/java/org/toop/framework/asset/resources/SoundEffectAsset.java similarity index 86% rename from framework/src/main/java/org/toop/framework/asset/resources/AudioAsset.java rename to framework/src/main/java/org/toop/framework/asset/resources/SoundEffectAsset.java index 4b00212..c9c81a8 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/AudioAsset.java +++ b/framework/src/main/java/org/toop/framework/asset/resources/SoundEffectAsset.java @@ -1,12 +1,15 @@ package org.toop.framework.asset.resources; +import javafx.scene.media.Media; + import javax.sound.sampled.*; import java.io.*; +import java.net.URI; @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); } diff --git a/framework/src/main/java/org/toop/framework/audio/SoundManager.java b/framework/src/main/java/org/toop/framework/audio/SoundManager.java index d870662..0dc1f72 100644 --- a/framework/src/main/java/org/toop/framework/audio/SoundManager.java +++ b/framework/src/main/java/org/toop/framework/audio/SoundManager.java @@ -1,9 +1,12 @@ package org.toop.framework.audio; +import javafx.application.Platform; +import javafx.scene.media.MediaPlayer; import org.toop.framework.SnowflakeGenerator; import org.toop.framework.asset.Asset; 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.eventbus.EventFlow; @@ -12,13 +15,17 @@ import java.util.*; import javax.sound.sampled.*; public class SoundManager { - private final Map activeClips = new HashMap<>(); - private final HashMap audioResources = new HashMap<>(); + private final List activeMusic = new ArrayList<>(); + private final Queue backgroundMusicQueue = new LinkedList<>(); + private final Map activeSoundEffects = new HashMap<>(); + private final HashMap audioResources = new HashMap<>(); private final SnowflakeGenerator idGenerator = new SnowflakeGenerator(); // TODO: Don't create a new generator + private double volume = 1.0; + public SoundManager() { // Get all Audio Resources and add them to a list. - for (Asset asset : AssetManager.getAllOfType(AudioAsset.class)) { + for (Asset asset : AssetManager.getAllOfType(SoundEffectAsset.class)) { try { this.addAudioResource(asset); } catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) { @@ -27,7 +34,9 @@ public class SoundManager { } new EventFlow() .listen(this::handlePlaySound) - .listen(this::handleStopSound); + .listen(this::handleStopSound) + .listen(this::handleMusicStart) + .listen(this::handleVolumeChange); } private void handlePlaySound(AudioEvents.PlayAudio event) { @@ -42,14 +51,70 @@ public class SoundManager { this.stopSound(event.clipId()); } - private void addAudioResource(Asset audioAsset) + private void addAudioResource(Asset audioAsset) throws IOException, UnsupportedAudioFileException, LineUnavailableException { 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 { - AudioAsset asset = audioResources.get(audioFileName); + SoundEffectAsset asset = audioResources.get(audioFileName); // Return -1 which indicates resource wasn't available if (asset == null){ @@ -71,12 +136,12 @@ public class SoundManager { long clipId = idGenerator.nextId(); // 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) clip.addLineListener(event -> { if (event.getType() == LineEvent.Type.STOP && !clip.isRunning()) { - activeClips.remove(clipId); + activeSoundEffects.remove(clipId); clip.close(); } }); @@ -86,7 +151,7 @@ public class SoundManager { } public void stopSound(long clipId) { - Clip clip = activeClips.get(clipId); + Clip clip = activeSoundEffects.get(clipId); if (clip == null) { return; @@ -94,14 +159,14 @@ public class SoundManager { clip.stop(); clip.close(); - activeClips.remove(clipId); + activeSoundEffects.remove(clipId); } public void stopAllSounds() { - for (Clip clip : activeClips.values()) { + for (Clip clip : activeSoundEffects.values()) { clip.stop(); clip.close(); } - activeClips.clear(); + activeSoundEffects.clear(); } } diff --git a/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java b/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java index 0f0e158..d7eecbb 100644 --- a/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java +++ b/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java @@ -1,5 +1,6 @@ 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.EventsBase; @@ -9,4 +10,8 @@ public class AudioEvents extends EventsBase { implements EventWithoutSnowflake {} public record StopAudio(long clipId) implements EventWithoutSnowflake {} + + public record StartBackgroundMusic() implements EventWithoutSnowflake {} + public record ChangeVolume(double newVolume) implements EventWithoutSnowflake {} + }