Hangman / TuningFork

Advanced 3D audio features for libGDX desktop users.
Apache License 2.0
31 stars 4 forks source link

changing theme in jukebox not working at runtime, but does work while debugging #12

Closed jeltedeproft closed 11 months ago

jeltedeproft commented 11 months ago

I have the following code

    case MUSIC_CHANGE_THEME:
        provider.setTheme(audioData.getTheme());
        jukeBox.softStopAndResume(Interpolation.linear, 2f);
        break;

When i play my game normally the song does not change. When I put a debug point here

    /**
     * Updates the JukeBox. This method should be called every frame.
     */
    public void update() {
        if (this.stopped) {
            this.handleEvents();
            return;
        }

        SongSource source = null;
        SongSettings settings = null;
        if (this.currentSong != null) {
            source = this.currentSong.getSource();
            settings = this.currentSong.getSettings();
        }

        if (source != null && source.isPlaying()) {
            final float fadeVolume = this.determineFadeVolume(source, settings);
            if (this.softStop) {
                this.softStopFade(source);  =====> HERE IS THE DEBUG POINT
            } else {
                source.setVolume(fadeVolume);
            }
        } else {
            this.resetSoftStop(true);
            this.eventSongEnd = this.currentSong;
            this.nextSong();
        }

        this.handleEvents();
    }

Then the debugger stops there and at that line the music stops and it changes to another song. Do you have any explanation for this?

Hangman commented 11 months ago

Hmm that's a tough one. The only thing I can think of that makes a difference between normal and debug mode is time. Like more time has passed until that line is run. If you could provide a minimal example project where the issue is reproducable, that would be great but isn't necessary of course. I'll dig into the issue tomorrow.

Thanks for reporting!

Hangman commented 11 months ago

I wasn't able to reproduce the issue so far. I was just trying to break the system in any way I could think of. Though, I might have missed the case that leads to your issue.

Are you by any chance calling

case MUSIC_CHANGE_THEME:
        provider.setTheme(audioData.getTheme());
        jukeBox.softStopAndResume(Interpolation.linear, 2f);
        break;

or jukebox.update() on different threads?

Could also be something simple on your side like accidently calling jukebox.stop() after jukebox.softStopAndResume(), streamed sounds could play in the background while the main thread is halted by the debugger.

I'd recommend to jukebox.addObserver(...) and log/print those events for debugging purposes. The debugger could lead to false results here as so much is based on time (fade in/out durations etc.).

Unfortunately, the jukebox events are fired in a kinda random order within a single frame. Not a big deal normally, but in this case it could cause some confusion. I've quickly written something that should spit out all events in proper order. implementation 'com.github.Hangman:TuningFork:proper_jukebox_events-SNAPSHOT' which also includes a quick test of the ThemePlayListProvider

If you prefer a chat over slow issue conversations, feel free to hit me up on discord => "mrwinterbottom".

jeltedeproft commented 11 months ago

Thank you so much for jumping on this issue so fast. I'm gonna investigate a bit more with your suggestions and I'll update this thread with my findings. (bit bussy atm)

jeltedeproft commented 11 months ago

Your first 2 suggestions doesn't seem to apply to me. They seem to be called from the same thread. I don't seem to be calling stop() after softStopAndResume().

How am I suposed to be using these events? Do I import your new version and then just run my game and check the logs, or?

jeltedeproft commented 11 months ago

hmm ok so i added the observer and logged all events, I only have this in the log

JukeBoxObserverLogger: onJukeBoxStart : 
JukeBoxObserverLogger: onPlayListStart first song : mainMenu
JukeBoxObserverLogger: onSongStart : mainMenu

When it is suposed to change playlist, nothing is logged

I do have some of my own code that is being logged, so my code is being executed

DEBUG: [d.e.c.s.ScreenManager             ]: Screen 'play' was pushed, using the transition 'luminance'
MusicManager: changed to theme : 0
MusicManager: jukeBox current song : de.pottgames.tuningfork.jukebox.song.Song@c7ba306
MusicManager: before update
Hangman commented 11 months ago

That could mean there's no corresponding PlayList for the chosen theme. If I do something like:

provider.add(playList0, 0);
// provider.add(playList1, 1); <- missing code
provider.setTheme(0);
JukeBox jukeBox = new JukeBox(provider);
jukeBox.play();

provider.setTheme(1);
jukeBox.softStopAndResume(Interpolation.linear, 2f);

I get the same behavior. I will add some some checks and warning logs for those cases after christmas.

Let's hope it's just this. If not, I'll be available again after the holidays. Merry Christmas

jeltedeproft commented 11 months ago

hmm that doesn't seem to be the case.

When I debug the themes seem to be set correctly

[0=de.pottgames.tuningfork.jukebox.playlist.PlayList@73194df, 3=de.pottgames.tuningfork.jukebox.playlist.PlayList@3a4621bd, 1=de.pottgames.tuningfork.jukebox.playlist.PlayList@6eb2384f, 4=de.pottgames.tuningfork.jukebox.playlist.PlayList@31dadd46, 2=de.pottgames.tuningfork.jukebox.playlist.PlayList@3c9c0d96]

Also my own logging says im adding the right themes

MusicManager: adding to provider playlist : de.pottgames.tuningfork.jukebox.playlist.PlayList@73194df
MusicManager: adding to provider theme : 0
MusicManager: adding to provider playlist : de.pottgames.tuningfork.jukebox.playlist.PlayList@6eb2384f
MusicManager: adding to provider theme : 1
MusicManager: adding to provider playlist : de.pottgames.tuningfork.jukebox.playlist.PlayList@3c9c0d96
MusicManager: adding to provider theme : 2
MusicManager: adding to provider playlist : de.pottgames.tuningfork.jukebox.playlist.PlayList@3a4621bd
MusicManager: adding to provider theme : 3
MusicManager: adding to provider playlist : de.pottgames.tuningfork.jukebox.playlist.PlayList@31dadd46
MusicManager: adding to provider theme : 4

And I can see in the code the theme is 0 and its updating it with 1

Okido, Merry christmas, enjoy your holidays

jeltedeproft commented 11 months ago

here is also my complete musicManager class, maybe you spot something off?

public class MusicManager implements Disposable, MusicManagerInterface {
    private static final String TAG = MusicManager.class.getSimpleName();

    private static MusicManagerInterface instance = null;
    private final Audio audio;
    private ThemePlayListProvider provider;
    private JukeBox jukeBox;

    private final Map<Integer, AudioData> audioEnumToAudioData = new HashMap<>();
    private final HashMap<String, Array<BufferedSoundSource>> loadedSounds = new HashMap<>();

    private MusicManager() {
        audio = Audio.init(new AudioConfig().setLogger((MultiFileLogger) Gdx.app.getApplicationLogger()));

        loadSounds();
        jukeBox = loadAndInitJukeBox();
        jukeBox.addObserver(new JukeBoxObserverLogger());

        jukeBox.play();
        jukeBox.update();
        Gdx.app.debug(TAG, "jukeBox starting song : " + jukeBox.getCurrentSong().getMeta().getTitle());
    }

    private void loadSounds() {
        loadAudio(AudioEnum::isSound, (fullFilePath, audioData) -> AssetManagerUtility.loadSoundBufferAsset(fullFilePath));
    }

    private void loadAudio(Predicate<AudioEnum> filter, BiConsumer<String, AudioData> loader) {
        for (AudioEnum audioEnum : AudioEnum.values()) {
            if (filter.test(audioEnum)) {
                AudioData audioData = getAudioData(audioEnum);
                audioData.getAudioFileName().forEach(fullFilePath -> loader.accept(fullFilePath, audioData));
            }
        }
    }

    private AudioData getAudioData(AudioEnum event) {
        return audioEnumToAudioData.computeIfAbsent(event.ordinal(), ordinal -> AudioFileReader.getAudioData().get(ordinal));
    }

    private JukeBox loadAndInitJukeBox() {
        Map<MusicTheme, PlayList> playlistsPerTheme = new EnumMap<>(MusicTheme.class);

        for (MusicTheme theme : MusicTheme.values()) {
            PlayList playlist = new PlayList();
            playlist.setLooping(true);
            playlist.setShuffleAfterPlaytrough(true);
            playlistsPerTheme.put(theme, playlist);
        }

        loadAudio(AudioEnum::isPlaylist, (fullFilePath, audioData) -> {
            StreamedSoundSource music = new StreamedSoundSource(Gdx.files.internal(fullFilePath));
            SongMeta meta = new SongMeta().setTitle(audioData.getName());
            SongSettings settings = SongSettings.linear(audioData.getVolume(), Constants.MUSIC_FADE_IN_DURATION, Constants.MUSIC_FADE_OUT_DURATION);
            Song song = new Song(music, settings, meta);
            playlistsPerTheme.get(MusicTheme.values()[audioData.getTheme()]).addSong(song);
        });

        playlistsPerTheme.values().forEach(PlayList::shuffle);

        provider = new ThemePlayListProvider();
        for (Entry<MusicTheme, PlayList> entry : playlistsPerTheme.entrySet()) {
            Gdx.app.debug(TAG, "adding to provider playlist : " + entry.getValue());
            Gdx.app.debug(TAG, "adding to provider theme : " + entry.getKey().ordinal());
            provider.add(entry.getValue(), entry.getKey().ordinal());
        }

        provider.setTheme(MusicTheme.MAIN_MENU.ordinal());

        return new JukeBox(provider);
    }

    @Override
    public void update(float delta) {
        jukeBox.update();
    }

    @Override
    public void sendCommand(AudioCommand command, AudioEnum event) {
        final AudioData audioData = getAudioData(event);
        final float volume = audioData.getVolume();
        switch (command) {
        case SOUND_PLAY_LOOP:
            playLoopedSound(audioData.getRandomAudioFileName(), volume, null);
            break;
        case SOUND_PLAY_ONCE:
            playAndForget(audioData.getRandomAudioFileName(), volume, null);
            break;
        case SOUND_STOP:
            stopAllSounds(audioData.getAudioFileName());
            break;
        case MUSIC_CHANGE_THEME:
            provider.setTheme(audioData.getTheme());
            jukeBox.softStopAndResume(Interpolation.linear, 2f);
            Gdx.app.debug(TAG, "changed to theme : " + audioData.getTheme());
            Gdx.app.debug(TAG, "jukeBox current song : " + jukeBox.getCurrentSong());
            break;
        case MUSIC_PAUSE:
            jukeBox.pause();
            break;
        case MUSIC_STOP:
            jukeBox.stop();
            break;
        default:
            break;
        }
    }

    private BufferedSoundSource playLoopedSound(String fullFilePath, float volume, CellPosition pos) {
        BufferedSoundSource sound = createBufferedSound(fullFilePath, volume);
        if (sound != null) {
            if (pos != null) {
                sound.setPosition(pos.x, pos.y, 0);
            }
            sound.play();
        }
        return sound;
    }

    private void playAndForget(String fullFilePath, float volume, CellPosition pos) {
        if (AssetManagerUtility.isAssetLoaded(fullFilePath)) {
            SoundBuffer soundBuffer = AssetManagerUtility.getSoundBufferAsset(fullFilePath);
            if (pos != null) {
                soundBuffer.play3D(volume, new Vector3(pos.x, pos.y, 0));
            } else {
                soundBuffer.play(volume);
            }
        }
    }

    private void stopAllSounds(List<String> audioFileNames) {
        audioFileNames.forEach(name -> {
            final Array<BufferedSoundSource> sounds = loadedSounds.get(name);
            if (sounds != null && sounds.size > 0) {
                sounds.get(0).stop();
                sounds.removeIndex(0);
            }
        });
    }

    private BufferedSoundSource createBufferedSound(String fullFilePath, float volume) {
        loadedSounds.computeIfAbsent(fullFilePath, s -> new Array<>());
        if (AssetManagerUtility.isAssetLoaded(fullFilePath)) {
            SoundBuffer soundBuffer = AssetManagerUtility.getSoundBufferAsset(fullFilePath);
            BufferedSoundSource bufferedSound = audio.obtainSource(soundBuffer);
            bufferedSound.setVolume(volume);
            bufferedSound.setLooping(true);
            loadedSounds.get(fullFilePath).add(bufferedSound);
            return bufferedSound;
        }
        Gdx.app.error(TAG, "Sound not loaded", null);
        return null;
    }

    public static MusicManagerInterface getInstance() {
        if (instance == null) {
            instance = new MusicManager();
        }
        return instance;
    }

    public static void setInstance(MusicManagerInterface newInstance) {
        instance = newInstance;
    }

    @Override
    public void dispose() {
        loadedSounds.values().forEach(array -> array.forEach(BufferedSoundSource::free));
        audio.dispose();
    }
}
Hangman commented 11 months ago

What are the values for Constants.MUSIC_FADE_IN_DURATION and Constants.MUSIC_FADE_OUT_DURATION?

When it is suposed to change playlist, nothing is logged

It first fades out the current song, during which no logs are generated. The interesting part starts after waiting for Constants.MUSIC_FADE_OUT_DURATION seconds when the PlayList change should occur. Does the jukebox

If it's the former, I can trigger this behavior with either no playlist for the theme or an empty playlist for the theme. If it's the latter, I don't really know what happens (unless Constants.MUSIC_FADE_OUT_DURATION is very high) and I can't reproduce this behavior. In this case a MRE would be very helpful.

Gdx.app.debug(TAG, "jukeBox current song : " + jukeBox.getCurrentSong()); will probably always print mainMenu at that point because the song isn't changed until the fade-out is complete, so that's expected.

JukeBoxObserverLogger: onJukeBoxStart : 
JukeBoxObserverLogger: onPlayListStart first song : mainMenu
JukeBoxObserverLogger: onSongStart : mainMenu

If you let it run longer, what are the next few jukebox logs after those 3 lines?

jeltedeproft commented 11 months ago

omg I'm so dumb, my main game screen didn't call update() on the musicManager. I'm so sorry for wasting your time with this. Should have investigated a bit more before posting here

jeltedeproft commented 11 months ago

works perfectly now

Hangman commented 11 months ago

Hey no worries, this is good news actually. Glad it works now!