How do i ensure preload() has finished?

i’m attempting to use p5.js for some audio processing and phaser.js for some game code.

function phaser_preload() {
    boop = loadSound('boop.wav');
}
function phaser_create() {
    fft = new p5.FFT();
    boop.play();
    fft.setInput(boop);
}
function phaser_update(time, dt) {
    fft.analyze();
}
new p5();
var game, boop, fft;
game = new Phaser.Game(config)

but boop.play() in phaser_create throws an error if boop hasn’t finished loading. is there a way to ensure that phaser_preload doesn’t return before loadSound has completely finished?

Your example code appears to be incomplete and unusually structured:

  • You create a p5 instance, but you don’t pass a callback to it
  • You don’t have a setup function
  • You don’t appear to call phaser_preload() anywhere.

If you want to leverage the p5.js preload functionality you either need to have a global preload function if using global mode, or pass a callback to the p5 constructor which defines a preload function. I suggest you review the docs covering sketch “Structure”. (See examples below)

If you have a properly structured sketch with a preload function, then any p5.js resources for which you initiate loading in that preload function will be fully loaded when setup is called.

An alternative would be to use the callback parameters of the loadSound function: loadSound(path, [successCallback], [errorCallback], [whileLoading]). If specified, either successCallback or errorCallback will be called when loading is complete.

In case you do want to use the preload function, here are some valid examples.

GlobalMode:

let boop;

function preload() {
  boop = loadSound('boop.wav');
}

function setup() {
  if (boop.isLoaded()) {
    // This should be printed to the console
    print('Can has boop!');
  }
}

// No need to create an instance of p5 the sketch should initialize automatically

Instance Mode:

function boopSketch(p) {
  let boop;

  p.preload = function() {
    boop = loadSound('boop.wav');
  }

  p.setup = function() {
    if (boop.isLoaded()) {
      // This should be printed to the console
      print('Can has boop!');
    }
  }
}

let instance = new p5(boopSketch);
1 Like

i ended up using a “”“reference”"" count:

loading = 0;
function preload() {
    loading += 1;
    boop = loadSound('boop.wav', () => loading -= 1);
}
async function begin() {
    new p5();
    while(loading > 0) {
        await new Promise(resolve => setTimeout(resolve, 100));
    }
    var game, boop;
    game = new Phaser.Game(config);
}
begin()

less hack-y solutions are definitely welcome though

i tried cutting out as much irrelevant code as i could

  • had no idea i was supposed to pass it a callback, the docs for on-demand global mode didn’t mention it
  • setup is being done in the phaser_create function
  • the phaser functions are called by phaser. they’re specified in a config that i didn’t include; full config below
var config = {
    type: Phaser.AUTO,
    width: 160*3,
    height: 144*3,
    backgroundColor: '#64a5ff',
    pixelArt: true,
    scene: {
        preload: phaser_preload,
        create: phaser_create,
        update: phaser_update
    },
};

i tried using a global preload function, but it would return before fully preloading the sound, and phaser would try to play boop before it was finished loading. i didn’t want to set up a whole sketch since i just need p5 for the fft bits

If you want to use global mode with preload I suggest you do something like:

let boop;
let game;

// p5.js invokes this automatically, do not invoke it yourself
function preload() {
  boop = loadSound('boop.wav');
}

// p5.js invokes this automatically, do not invoke it yourself
function setup() {
  noCanvas();
  // At this point you are guaranteed that boop is loaded.
  game = new Phaser.Game(config);
  // Any code that expects game to have been initialized should be called from here.
}

From my testing I found the following:

  • If there is no global setup function and no call to the p5 constructor, then p5.sound is not loaded and preload is never called.
  • With the call to noCanvas() in setup, there isn’t any perceptible overhead to this approach (without it p5.js does create a default canvas element).
  • Calling the parameterless p5 constructor forces the initialization of p5.js in global mode even if there is no setup function, but this includes the default overhead of creating a canvas and also generates a warning p5.js seems to have been imported multiple times. Please remove the duplicate import

If you do want to use loadSound in async code, this is how you should do it, FYI:

function setup() {
	noCanvas();
	notASketch();
}

async function notASketch() {
	// This is how you turn a function that takes callbacks into a promise:
	let loadSoundPromise = new Promise((resolve, reject) => loadSound('DING.WAV', resolve, reject));
	try {
		// Note: I created the Promise separately for clarity, but you could just inline it here.
		let ding = await loadSoundPromise;
		
		// This is always true, just here for demonstration purposes.
		if (ding.isLoaded()) {
			console.log('DING!');
		} else {
			console.warn(':-(');
		}
	} catch (e) {
		console.warn('loading failed.');
		console.error(e.message + '\n' + e.stack);
	}
}

However, keep in mind, you still can’t call this without first ensuring that setup has been called, because until setup is called there is no guarantee that loadSound will be declared yet.

1 Like

just note that that is useful (as written in the guideline) while you should still keep the code runnable - posting a snippet (like you did) and posting a runnable, concise code are different things (and I prefer the latter).

I think KumuPaul’s answer is good - I think p5.js doesn’t have promise for preload (which is a bit problematic in some cases) but virtually you can use setup for that - because it ensures that setup is called after preload is done.

Another way can be, in phaser, simply skip the update loop until the audio file is loaded.

1 Like

thank you! that preload/setup code is exactly what i needed, and i appreciate the async example

thank you for letting me know, i’ll post full runnable code next time :slight_smile: