Improve music fade and mock
This commit is contained in:
@@ -15,17 +15,19 @@ public class AcousticHandler {
|
|||||||
private ArrayList<MusicAsset> gameTracks = new ArrayList<>();
|
private ArrayList<MusicAsset> gameTracks = new ArrayList<>();
|
||||||
private NanoTimer trackTimer = new NanoTimer();
|
private NanoTimer trackTimer = new NanoTimer();
|
||||||
|
|
||||||
private boolean fading = false;
|
private boolean fading = false; // Indicates if a fade is in progress
|
||||||
private NanoTimer fadeTimer = new NanoTimer();
|
private boolean outFadingFinished = false; // Tracks the completion of the outfade
|
||||||
private static final float FADE_DURATION = 3.0f;
|
private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress
|
||||||
private static final float CROSSFADE_DURATION = 1.5f;
|
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 mainVolume = 1.0f;
|
||||||
private float musicVolume = 1.0f;
|
private float musicVolume = 1.0f;
|
||||||
private float soundVolume = 1.0f;
|
private float soundVolume = 1.0f;
|
||||||
|
|
||||||
private GameMusic scheduled = null;
|
|
||||||
private GameMusic playing = null;
|
|
||||||
private ArrayList<GameSound> sounds = new ArrayList<>();
|
private ArrayList<GameSound> sounds = new ArrayList<>();
|
||||||
|
|
||||||
public AcousticHandler(MdgaApp app) {
|
public AcousticHandler(MdgaApp app) {
|
||||||
@@ -103,8 +105,8 @@ public void playState(MdgaState state) {
|
|||||||
case GAME:
|
case GAME:
|
||||||
addGameTracks();
|
addGameTracks();
|
||||||
playGame = true;
|
playGame = true;
|
||||||
assert (gameTracks.size() > 0) : "no more game music available";
|
assert (!gameTracks.isEmpty()) : "no more game music available";
|
||||||
asset = gameTracks.remove(0);
|
asset = gameTracks.removeFirst();
|
||||||
break;
|
break;
|
||||||
case CEREMONY:
|
case CEREMONY:
|
||||||
playGame = false;
|
playGame = false;
|
||||||
@@ -116,7 +118,7 @@ public void playState(MdgaState state) {
|
|||||||
|
|
||||||
assert (null != asset) : "music sceduling went wrong";
|
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.
|
* Updates the music playback state, handling transitions between tracks with fades.
|
||||||
* This method is responsible for fading out the currently playing track,
|
*
|
||||||
* fading in the scheduled track, and handling crossfade between the two tracks.
|
* 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() {
|
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;
|
playing = scheduled;
|
||||||
scheduled = null;
|
scheduled = null;
|
||||||
playing.play();
|
playing.play();
|
||||||
|
playing.update(getMusicVolumeTotal()); // Start at full volume
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheduled != null && !fading) {
|
if (scheduled != null && !fading) {
|
||||||
|
// Start a new fade process if no fade is ongoing
|
||||||
fading = true;
|
fading = true;
|
||||||
fadeTimer.reset();
|
fadeTimer.reset();
|
||||||
|
old = playing;
|
||||||
|
playing = null; // Clear current track until fade-in begins
|
||||||
|
outFadingFinished = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fading) {
|
if (fading) {
|
||||||
float time = fadeTimer.getTimeInSeconds();
|
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 t = Math.min(time / FADE_DURATION, 1.0f);
|
||||||
float oldVolume = lerp(1.0f, 0.0f, t);
|
float oldVolume = lerp(1.0f, 0.0f, t);
|
||||||
if (playing != null) {
|
old.update(getMusicVolumeTotal() * oldVolume);
|
||||||
playing.update(getMusicVolumeTotal() * oldVolume);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time > FADE_DURATION && time <= FADE_DURATION + CROSSFADE_DURATION) {
|
if (old != null && time > FADE_DURATION) {
|
||||||
float t = Math.min((time - FADE_DURATION) / CROSSFADE_DURATION, 1.0f);
|
// Complete the outfade for the old track
|
||||||
|
old.pause();
|
||||||
|
old = null;
|
||||||
|
outFadingFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
if (scheduled != null && infadeTime <= CROSSFADE_DURATION) {
|
||||||
|
float t = Math.min(infadeTime / CROSSFADE_DURATION, 1.0f);
|
||||||
float newVolume = lerp(0.0f, 1.0f, t);
|
float newVolume = lerp(0.0f, 1.0f, t);
|
||||||
|
|
||||||
if (!scheduled.isPlaying()) {
|
if (playing != scheduled) {
|
||||||
scheduled.play();
|
|
||||||
}
|
|
||||||
scheduled.update(getMusicVolumeTotal() * newVolume);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time > FADE_DURATION + CROSSFADE_DURATION) {
|
|
||||||
if (playing != null) {
|
|
||||||
playing.pause();
|
|
||||||
}
|
|
||||||
playing = scheduled;
|
playing = scheduled;
|
||||||
scheduled = null;
|
playing.play();
|
||||||
|
}
|
||||||
|
playing.update(getMusicVolumeTotal() * newVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infadeTime > CROSSFADE_DURATION) {
|
||||||
|
// Infade is complete, finalize state
|
||||||
fading = false;
|
fading = false;
|
||||||
|
if (playing != null) {
|
||||||
|
playing.update(getMusicVolumeTotal()); // Ensure playing track is at full volume
|
||||||
|
}
|
||||||
|
scheduled = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (playing != null) {
|
} else {
|
||||||
|
// Regular playback state, ensure playing track is updated
|
||||||
|
if (playing != null) {
|
||||||
playing.update(getMusicVolumeTotal());
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.
|
* a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed.
|
||||||
*/
|
*/
|
||||||
private void updateGameTracks() {
|
private void updateGameTracks() {
|
||||||
|
if(null == playing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (playing.nearEnd(10)) {
|
if (playing.nearEnd(10)) {
|
||||||
if (gameTracks.isEmpty()) {
|
if (gameTracks.isEmpty()) {
|
||||||
addGameTracks();
|
addGameTracks();
|
||||||
@@ -214,7 +283,7 @@ private void updateGameTracks() {
|
|||||||
|
|
||||||
MusicAsset nextTrack = gameTracks.remove(0);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class GameMusic {
|
|||||||
private float volume;
|
private float volume;
|
||||||
private final float subVolume;
|
private final float subVolume;
|
||||||
private final AudioNode music;
|
private final AudioNode music;
|
||||||
|
private float pause;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new GameMusic object.
|
* 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 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.
|
* @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.volume = volume;
|
||||||
this.subVolume = subVolume;
|
this.subVolume = subVolume;
|
||||||
|
this.pause = pause;
|
||||||
|
|
||||||
music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream);
|
music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream);
|
||||||
music.setPositional(false);
|
music.setPositional(false);
|
||||||
@@ -107,4 +109,8 @@ void update(float newVolume) {
|
|||||||
music.setVolume(volume * subVolume);
|
music.setVolume(volume * subVolume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float getPause() {
|
||||||
|
return pause;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
* 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 {
|
enum MusicAsset {
|
||||||
MAIN_MENU("Spaceship.wav", 1.0f),
|
MAIN_MENU("Spaceship.wav", true, 1.0f),
|
||||||
LOBBY("DeadPlanet.wav", 1.0f),
|
LOBBY("DeadPlanet.wav", true, 1.0f),
|
||||||
CEREMONY("80s,Disco,Life.wav", 1.0f),
|
CEREMONY("80s,Disco,Life.wav", true, 1.0f),
|
||||||
GAME_1("NeonRoadTrip.wav", false, 1.0f),
|
GAME_1("NeonRoadTrip.wav", 1.0f),
|
||||||
GAME_2("NoPressureTrance.wav", false, 1.0f),
|
GAME_2("NoPressureTrance.wav", 1.0f),
|
||||||
GAME_3("TheSynthRave.wav", false, 1.0f),
|
GAME_3("TheSynthRave.wav", 1.0f),
|
||||||
GAME_4("LaserParty.wav", false, 1.0f),
|
GAME_4("LaserParty.wav", 1.0f),
|
||||||
GAME_5("RetroNoir.wav", false, 1.0f),
|
GAME_5("RetroNoir.wav", 1.0f),
|
||||||
GAME_6("SpaceInvaders.wav", false, 1.0f);
|
GAME_6("SpaceInvaders.wav", 1.0f);
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
private final boolean loop;
|
private final boolean loop;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ private void setupAwardCeremony() {
|
|||||||
awardCeremonyNode = new Node("AwardCeremonyNode");
|
awardCeremonyNode = new Node("AwardCeremonyNode");
|
||||||
|
|
||||||
// Add a background for the award ceremony
|
// Add a background for the award ceremony
|
||||||
Geometry background = createBackground("b1.jpg");
|
Geometry background = createBackground("b1.png");
|
||||||
awardCeremonyNode.attachChild(background);
|
awardCeremonyNode.attachChild(background);
|
||||||
|
|
||||||
// Create a container for the UI elements
|
// Create a container for the UI elements
|
||||||
@@ -74,7 +74,7 @@ private void setupStatistics() {
|
|||||||
statisticsNode = new Node("StatisticsNode");
|
statisticsNode = new Node("StatisticsNode");
|
||||||
|
|
||||||
// Add a background for the statistics
|
// Add a background for the statistics
|
||||||
Geometry background = createBackground("b2.jpg");
|
Geometry background = createBackground("b2.png");
|
||||||
statisticsNode.attachChild(background);
|
statisticsNode.attachChild(background);
|
||||||
|
|
||||||
// Create a container for the statistics UI
|
// Create a container for the statistics UI
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
package pp.mdga.client;
|
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;
|
import pp.mdga.client.Board.BoardHandler;
|
||||||
|
|
||||||
public class GameView extends MdgaView {
|
public class GameView extends MdgaView {
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ public LobbyView(MdgaApp app) {
|
|||||||
@Override
|
@Override
|
||||||
public void enter() {
|
public void enter() {
|
||||||
// Add a background
|
// Add a background
|
||||||
Geometry background = createBackground("sky.jpg");
|
Geometry background = createBackground("lobby.png");
|
||||||
rootNode.attachChild(background);
|
rootNode.attachChild(background);
|
||||||
|
|
||||||
// Add color selection boxes
|
// Add color selection boxes
|
||||||
float boxSize = 100;
|
float boxSize = 200;
|
||||||
float spacing = 20;
|
float spacing = 60;
|
||||||
float totalWidth = 4 * boxSize + 3 * spacing;
|
float totalWidth = 4 * boxSize + 3 * spacing;
|
||||||
float startX = (app.getCamera().getWidth() - totalWidth) / 2;
|
float startX = (app.getCamera().getWidth() - totalWidth) / 2;
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
Geometry colorBox = createColorBox(startX + i * (boxSize + spacing), app.getCamera().getHeight() / 2 - boxSize / 2, boxSize);
|
//Geometry colorBox = createColorBox(startX + i * (boxSize + spacing), app.getCamera().getHeight() / 2 - boxSize / 2, boxSize);
|
||||||
rootNode.attachChild(colorBox);
|
//rootNode.attachChild(colorBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.getGuiNode().attachChild(rootNode);
|
app.getGuiNode().attachChild(rootNode);
|
||||||
@@ -68,7 +68,7 @@ private Geometry createColorBox(float x, float y, float size) {
|
|||||||
Quad quad = new Quad(size, size);
|
Quad quad = new Quad(size, size);
|
||||||
Geometry geom = new Geometry("ColorBox", quad);
|
Geometry geom = new Geometry("ColorBox", quad);
|
||||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
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.setMaterial(mat);
|
||||||
geom.setLocalTranslation(x, y, 0);
|
geom.setLocalTranslation(x, y, 0);
|
||||||
return geom;
|
return geom;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public void simpleInitApp() {
|
|||||||
public void simpleUpdate(float tpf) {
|
public void simpleUpdate(float tpf) {
|
||||||
acousticHandler.update();
|
acousticHandler.update();
|
||||||
|
|
||||||
if(test.getTimeInSeconds() < 10) {
|
if(test.getTimeInSeconds() < 8) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
BIN
Projekte/mdga/client/src/main/resources/b1.png
Normal file
BIN
Projekte/mdga/client/src/main/resources/b1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
BIN
Projekte/mdga/client/src/main/resources/b2.png
Normal file
BIN
Projekte/mdga/client/src/main/resources/b2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
BIN
Projekte/mdga/client/src/main/resources/lobby.png
Normal file
BIN
Projekte/mdga/client/src/main/resources/lobby.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 274 KiB |
Reference in New Issue
Block a user