diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java index 220d0c18..0cd2d463 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java @@ -15,17 +15,19 @@ public class AcousticHandler { private ArrayList gameTracks = new ArrayList<>(); private NanoTimer trackTimer = new NanoTimer(); - private boolean fading = false; - private NanoTimer fadeTimer = new NanoTimer(); - private static final float FADE_DURATION = 3.0f; - private static final float CROSSFADE_DURATION = 1.5f; + private boolean fading = false; // Indicates if a fade is in progress + private boolean outFadingFinished = false; // Tracks the completion of the outfade + private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress + private static final float FADE_DURATION = 3.0f; // Duration for outfade + private static final float CROSSFADE_DURATION = 1.5f; // Duration for infade + private GameMusic playing = null; // Currently playing track + private GameMusic scheduled = null; // Scheduled track to play next + private GameMusic old = null; // Old track being faded out private float mainVolume = 1.0f; private float musicVolume = 1.0f; private float soundVolume = 1.0f; - private GameMusic scheduled = null; - private GameMusic playing = null; private ArrayList sounds = new ArrayList<>(); public AcousticHandler(MdgaApp app) { @@ -103,8 +105,8 @@ public void playState(MdgaState state) { case GAME: addGameTracks(); playGame = true; - assert (gameTracks.size() > 0) : "no more game music available"; - asset = gameTracks.remove(0); + assert (!gameTracks.isEmpty()) : "no more game music available"; + asset = gameTracks.removeFirst(); break; case CEREMONY: playGame = false; @@ -116,7 +118,7 @@ public void playState(MdgaState state) { assert (null != asset) : "music sceduling went wrong"; - scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop()); + scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), 0.0f); } /** @@ -132,56 +134,119 @@ private float lerp(float start, float end, float t) { } /** - * Updates the current volume and handles track crossfading logic. - * This method is responsible for fading out the currently playing track, - * fading in the scheduled track, and handling crossfade between the two tracks. + * Updates the music playback state, handling transitions between tracks with fades. + * + * This function ensures smooth transitions between tracks, including fading out the + * current track, pausing, and fading in the next scheduled track. It also starts + * music instantly if nothing is currently playing and a track is scheduled. + * + * Function Behavior: + * 1. If no track is playing and a track is scheduled, it starts instantly. + * 2. If a track is playing and a new track is scheduled, it initiates a fade-out + * of the current track, followed by a pause (if applicable), and a fade-in of the new track. + * 3. If a fade process is ongoing, it respects the fade durations and smoothly transitions + * between tracks. + * 4. Tracks that finish their fade-out are paused, and their volume is set to 0. + * 5. During regular playback, the current track's volume is updated to match the overall + * music volume setting. + * + * Special Cases: + * - If a new track is scheduled during an ongoing fade, it interrupts the current fade + * and starts the new fade sequence. + * - Handles null checks for playing and scheduled tracks to prevent errors. + * - Instant start of music is prioritized if nothing is playing. + * + * Preconditions: + * - `playing`, `scheduled`, and `old` may be null, and the function gracefully handles these states. + * - Fade durations and pause durations are configurable via `FADE_DURATION` and `CROSSFADE_DURATION`. + * + * Dependencies: + * - `getMusicVolumeTotal()`: Provides the total volume scaling factor for music. + * - `GameMusic` class methods: `play()`, `pause()`, `update(float volume)`, and `getPause()` for managing track playback. + * - `NanoTimer`: Used to track the elapsed time during fades. + * + * Exceptions: + * - Ensures no NullPointerException occurs by validating track states before invoking methods. */ private void updateVolumeAndTrack() { - if (playing == null && scheduled != null && !fading) { + if (scheduled == null && !fading && playing == null) { + // Nothing to play or fade, early exit + return; + } + + if (scheduled != null && playing == null && !fading) { + // Start the scheduled music instantly if nothing is currently playing playing = scheduled; scheduled = null; playing.play(); + playing.update(getMusicVolumeTotal()); // Start at full volume return; } if (scheduled != null && !fading) { + // Start a new fade process if no fade is ongoing fading = true; fadeTimer.reset(); + old = playing; + playing = null; // Clear current track until fade-in begins + outFadingFinished = false; } if (fading) { float time = fadeTimer.getTimeInSeconds(); - if (time <= FADE_DURATION) { + if (old != null && time <= FADE_DURATION) { + // Handle outfade for the old track float t = Math.min(time / FADE_DURATION, 1.0f); float oldVolume = lerp(1.0f, 0.0f, t); - if (playing != null) { - playing.update(getMusicVolumeTotal() * oldVolume); - } + old.update(getMusicVolumeTotal() * oldVolume); } - if (time > FADE_DURATION && time <= FADE_DURATION + CROSSFADE_DURATION) { - float t = Math.min((time - FADE_DURATION) / CROSSFADE_DURATION, 1.0f); - float newVolume = lerp(0.0f, 1.0f, t); - - if (!scheduled.isPlaying()) { - scheduled.play(); - } - scheduled.update(getMusicVolumeTotal() * newVolume); + if (old != null && time > FADE_DURATION) { + // Complete the outfade for the old track + old.pause(); + old = null; + outFadingFinished = true; } - if (time > FADE_DURATION + CROSSFADE_DURATION) { - if (playing != null) { - playing.pause(); - } - playing = scheduled; - scheduled = null; + // Respect the pause duration between outfade and infade + float pause = (scheduled != null) ? scheduled.getPause() : 0.0f; + if (time > FADE_DURATION + pause) { + // Start infade for the scheduled track + float infadeTime = time - FADE_DURATION - pause; - fading = false; + if (scheduled != null && infadeTime <= CROSSFADE_DURATION) { + float t = Math.min(infadeTime / CROSSFADE_DURATION, 1.0f); + float newVolume = lerp(0.0f, 1.0f, t); + + if (playing != scheduled) { + playing = scheduled; + playing.play(); + } + playing.update(getMusicVolumeTotal() * newVolume); + } + + if (infadeTime > CROSSFADE_DURATION) { + // Infade is complete, finalize state + fading = false; + if (playing != null) { + playing.update(getMusicVolumeTotal()); // Ensure playing track is at full volume + } + scheduled = null; + } + } + } else { + // Regular playback state, ensure playing track is updated + if (playing != null) { + playing.update(getMusicVolumeTotal()); + } else if (scheduled != null) { + // Handle immediate track switch + fading = true; + fadeTimer.reset(); + old = playing; + playing = null; // Clear current track to start fade process + outFadingFinished = false; } - } - else if (playing != null) { - playing.update(getMusicVolumeTotal()); } } @@ -203,6 +268,10 @@ private void addGameTracks() { * a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed. */ private void updateGameTracks() { + if(null == playing) { + return; + } + if (playing.nearEnd(10)) { if (gameTracks.isEmpty()) { addGameTracks(); @@ -214,7 +283,7 @@ private void updateGameTracks() { MusicAsset nextTrack = gameTracks.remove(0); - scheduled = new GameMusic(app, nextTrack, getMusicVolumeTotal(), nextTrack.getSubVolume(), nextTrack.getLoop()); + scheduled = new GameMusic(app, nextTrack, getMusicVolumeTotal(), nextTrack.getSubVolume(), nextTrack.getLoop(), 0.0f); } } diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java index 244f2b4e..7016406e 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java @@ -14,6 +14,7 @@ class GameMusic { private float volume; private final float subVolume; private final AudioNode music; + private float pause; /** * Constructs a new GameMusic object. @@ -24,9 +25,10 @@ class GameMusic { * @param subVolume A relative volume that modifies the base music volume, typically a percentage. * @param loop A flag indicating whether the music should loop once it finishes. */ - GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop) { + GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop, float pause) { this.volume = volume; this.subVolume = subVolume; + this.pause = pause; music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream); music.setPositional(false); @@ -107,4 +109,8 @@ void update(float newVolume) { music.setVolume(volume * subVolume); } } + + float getPause() { + return pause; + } } diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java index 691fbe70..165138e7 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java @@ -7,15 +7,15 @@ * These music assets are used to control the music that plays in different parts of the game, such as menus and in-game music. */ enum MusicAsset { - MAIN_MENU("Spaceship.wav", 1.0f), - LOBBY("DeadPlanet.wav", 1.0f), - CEREMONY("80s,Disco,Life.wav", 1.0f), - GAME_1("NeonRoadTrip.wav", false, 1.0f), - GAME_2("NoPressureTrance.wav", false, 1.0f), - GAME_3("TheSynthRave.wav", false, 1.0f), - GAME_4("LaserParty.wav", false, 1.0f), - GAME_5("RetroNoir.wav", false, 1.0f), - GAME_6("SpaceInvaders.wav", false, 1.0f); + MAIN_MENU("Spaceship.wav", true, 1.0f), + LOBBY("DeadPlanet.wav", true, 1.0f), + CEREMONY("80s,Disco,Life.wav", true, 1.0f), + GAME_1("NeonRoadTrip.wav", 1.0f), + GAME_2("NoPressureTrance.wav", 1.0f), + GAME_3("TheSynthRave.wav", 1.0f), + GAME_4("LaserParty.wav", 1.0f), + GAME_5("RetroNoir.wav", 1.0f), + GAME_6("SpaceInvaders.wav", 1.0f); private final String path; private final boolean loop; diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/CeremonyView.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/CeremonyView.java index 880e7475..7fa4caf9 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/CeremonyView.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/CeremonyView.java @@ -49,7 +49,7 @@ private void setupAwardCeremony() { awardCeremonyNode = new Node("AwardCeremonyNode"); // Add a background for the award ceremony - Geometry background = createBackground("b1.jpg"); + Geometry background = createBackground("b1.png"); awardCeremonyNode.attachChild(background); // Create a container for the UI elements @@ -74,7 +74,7 @@ private void setupStatistics() { statisticsNode = new Node("StatisticsNode"); // Add a background for the statistics - Geometry background = createBackground("b2.jpg"); + Geometry background = createBackground("b2.png"); statisticsNode.attachChild(background); // Create a container for the statistics UI diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/GameView.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/GameView.java index 24d01ee8..9d692e1d 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/GameView.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/GameView.java @@ -1,12 +1,5 @@ package pp.mdga.client; -import com.jme3.material.Material; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.shape.Quad; -import com.simsilica.lemur.Button; -import com.simsilica.lemur.Container; -import com.simsilica.lemur.Label; import pp.mdga.client.Board.BoardHandler; public class GameView extends MdgaView { diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/LobbyView.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/LobbyView.java index 1ca22f2f..b4b5ee1c 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/LobbyView.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/LobbyView.java @@ -17,18 +17,18 @@ public LobbyView(MdgaApp app) { @Override public void enter() { // Add a background - Geometry background = createBackground("sky.jpg"); + Geometry background = createBackground("lobby.png"); rootNode.attachChild(background); // Add color selection boxes - float boxSize = 100; - float spacing = 20; + float boxSize = 200; + float spacing = 60; float totalWidth = 4 * boxSize + 3 * spacing; float startX = (app.getCamera().getWidth() - totalWidth) / 2; for (int i = 0; i < 4; i++) { - Geometry colorBox = createColorBox(startX + i * (boxSize + spacing), app.getCamera().getHeight() / 2 - boxSize / 2, boxSize); - rootNode.attachChild(colorBox); + //Geometry colorBox = createColorBox(startX + i * (boxSize + spacing), app.getCamera().getHeight() / 2 - boxSize / 2, boxSize); + //rootNode.attachChild(colorBox); } app.getGuiNode().attachChild(rootNode); @@ -68,7 +68,7 @@ private Geometry createColorBox(float x, float y, float size) { Quad quad = new Quad(size, size); Geometry geom = new Geometry("ColorBox", quad); Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", ColorRGBA.White); // Example texture/color + mat.setColor("Color", ColorRGBA.Gray); // Example texture/color geom.setMaterial(mat); geom.setLocalTranslation(x, y, 0); return geom; diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java index dace62af..73939f37 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java @@ -47,7 +47,7 @@ public void simpleInitApp() { public void simpleUpdate(float tpf) { acousticHandler.update(); - if(test.getTimeInSeconds() < 10) { + if(test.getTimeInSeconds() < 8) { return; } diff --git a/Projekte/mdga/client/src/main/resources/b1.jpg b/Projekte/mdga/client/src/main/resources/b1.jpg deleted file mode 100644 index a54babfc..00000000 Binary files a/Projekte/mdga/client/src/main/resources/b1.jpg and /dev/null differ diff --git a/Projekte/mdga/client/src/main/resources/b1.png b/Projekte/mdga/client/src/main/resources/b1.png new file mode 100644 index 00000000..99b070f8 Binary files /dev/null and b/Projekte/mdga/client/src/main/resources/b1.png differ diff --git a/Projekte/mdga/client/src/main/resources/b2.jpg b/Projekte/mdga/client/src/main/resources/b2.jpg deleted file mode 100644 index 31b161c1..00000000 Binary files a/Projekte/mdga/client/src/main/resources/b2.jpg and /dev/null differ diff --git a/Projekte/mdga/client/src/main/resources/b2.png b/Projekte/mdga/client/src/main/resources/b2.png new file mode 100644 index 00000000..a545aec5 Binary files /dev/null and b/Projekte/mdga/client/src/main/resources/b2.png differ diff --git a/Projekte/mdga/client/src/main/resources/lobby.png b/Projekte/mdga/client/src/main/resources/lobby.png new file mode 100644 index 00000000..86bb1a69 Binary files /dev/null and b/Projekte/mdga/client/src/main/resources/lobby.png differ diff --git a/Projekte/mdga/client/src/main/resources/sky.jpg b/Projekte/mdga/client/src/main/resources/sky.jpg deleted file mode 100644 index 51333fed..00000000 Binary files a/Projekte/mdga/client/src/main/resources/sky.jpg and /dev/null differ