Cross fading soundtracks

I have created two soundtracks for my game. Some scenes utilize soundtrack 1, while others make use of soundtrack 2. I have no problem pausing a soundtrack and resuming the other soundtrack when switching between scenes of different soundtracks.

However, I really want to fadeout the volume of the outgoing soundtrack rather than abruptly stopping it, and that’s been a struggle for several days now. It seems that I have to wait until the volume reaches 0 before using this.scene.start of the new scene. Otherwise, if I switch scenes and then try to fade out, the sound just keeps playing (despite console.log “showing” the volume decreasing).

I’ve tried fading before this.scene.start (this works up until the new scene begins), and I’ve tried fading after this.scene.start (logs suggest it is fading but in actuality nothing is happening). I’ve placed both soundtracks into global objects, so I would believe I have access to the old soundtrack after moving to the new scene…

Is there some baked-in functionality of Phaser 3 that limits a method for a scene once you transition away from that scene? Or if I start music in one scene, can I no longer access its properties once that scene is no longer the “active” scene?

I’m somewhat new to coding, and I’m guessing there’s some fundamental approach to “background processes” where I can have something execute that will continue even when not active. Just having trouble wrapping my head around why my logs show the volume going down but once the scene transitions, the actual volume ceases to adjust and the old music continues to play. I’ve rewritten my approach 5 times now, and nothing is working and the chatbots and I are out of ideas.

What’s the code?

Everything below is a spinoff game.js file to experiment. Here’s the global stuff:

var soundtrack1 = {	"name":				'soundtrack1'
			,	"tracklist": 		['track01', 'track05', 'track03', 'track04', 'track06']
			,	"trackIndex":		0
			,	"trackPosition":	0
			,	"track":			null
			};
var soundtrack2 = {	"name":				'soundtrack2'
			,	"tracklist": 		['track02']
			,	"trackIndex":		0
			,	"trackPosition":	0
			,	"track":			null
			};
var soundtracks = [soundtrack1,soundtrack2];

const 	sceneInfo 		= {						// Global object to store scene information
    sceneHome:	{ displayName: 'sceneHome'														},
	SceneA:		{ displayName: 'SceneA'	, playlist: 'soundtrack1'	, hotkey: 'keydown-A'		},
	SceneB:		{ displayName: 'SceneB'	, playlist: 'soundtrack1'	, hotkey: 'keydown-B'		},
	SceneC:		{ displayName: 'SceneC'	, playlist: 'soundtrack1'	, hotkey: 'keydown-C'		},
	SceneD:		{ displayName: 'SceneD'	, playlist: 'soundtrack2'	, hotkey: 'keydown-D'		},
	SceneE:		{ displayName: 'SceneE'	, playlist: 'soundtrack2'	, hotkey: 'keydown-E'		},
	SceneF:		{ displayName: 'SceneF'	, playlist: 'soundtrack2'	, hotkey: 'keydown-F'		}
};

Then, each of the scenesA-F extend from a baseScene and have one line in the create() and one line in the update() that relate to the soundtracks:

class SceneA extends sceneHome {
    constructor() {super({ key: 'SceneA' });}
		
	create() {
		super.create();
        this.playSoundtrack(soundtrack1);
    }
	
	update() {
        this.updateSoundtrack(soundtrack1);
    }
}

The purpose of this.updateSoundtrack is to keep track of next track and track position in my global objects.

My sceneHome, which A-F extend, has buttons A-F:

for (let i = 0; i < 6; i++) {
            let sceneKey = 'Scene' + String.fromCharCode(65 + i); // Convert ASCII code to character ('A' to 'F')
            let button = this.add.text(400, 60 + i * 40, 'Go to ' + sceneKey, { fontSize: '24px', fill: '#fff' });
            button.setOrigin(0.5);
            button.setInteractive();
            button.on('pointerdown', () => {
				this.playSound(this.sounds.click);
				this.sceneTransition(sceneKey);
            });
        }

This.sceneTransition’s purpose is to get the destination location, look up the soundtrack. If it’s different, begin the fadeout process. Then do a this.scene.start for the destination.

fadeoutSoundtrack(soundtrack) {
		if (soundtrack.track[soundtrack.trackIndex]) {
			console.log("object to fade out exists:",soundtrack.track[soundtrack.trackIndex]);
			// Set initial volume level
			let volume = soundtrack.track[soundtrack.trackIndex].volume;
			//console.log("current volume:",volume);
			
			// Define fade out duration and interval
			const fadeOutDuration = 2000; // 2 seconds
			const fadeOutInterval = 50; // 50 milliseconds

			// Calculate volume decrement per interval
			const volumeDecrement = (volume / (fadeOutDuration / fadeOutInterval)).toFixed(3);

			// Define a function to decrease volume gradually
			const decreaseVolume = () => {
				volume -= volumeDecrement;
				if (volume <= 0.08) {
					// Volume reached 0, stop the playlist
					soundtrack.track[soundtrack.trackIndex].stop();
					//console.log("STOPPED?",soundtrack);
				} else {
					// Set the new volume level
					soundtrack.track[soundtrack.trackIndex].setVolume(volume);
					console.log("fade out volume:",(soundtrack.track[soundtrack.trackIndex].volume).toFixed(3));
					// Call the function again after the interval
					setTimeout(decreaseVolume, fadeOutInterval);
				}
			};

			// Start the fade out process
			//decreaseVolume();
			// Start the fade out process and return the process reference
			return setTimeout(decreaseVolume, fadeOutInterval);
		}
	}

I think I’m going to try a version where I start all soundtracks upon game load, and then pause them all and set volumes to 0. Then, whenever a scene begins, I find the active soundtrack and tween the volume to while unpausing and tween everything else to 0 and paused. If I put that in the sceneHome that everything extends from, it should be modular, and the tween is set to execute whenever the scene has transitioned. I feel like this would bypass my “access” issue because all soundtracks will have been started at the get go.

I think tweens are a good idea. A paused, sleeping, or stopped scene won’t run tweens, so you might make a controller scene to do this.

All sound playback is global, so there’s no problem switching scenes per se.