Let’s say we have
const sound = await BABYLON.CreateSoundAsync("sound", "https://assets.babylonjs.com/sound/alarm-1.mp3", { maxInstances: 1 });
sound.play();
Now, if we play the second instance while the first one is still playing, the first one will stop and the second one will start.
I would like to have the setting that allows to just reject new instances if the maximum capacity is reached.
Why it’s important?
Imagine the cluster of 500 revenant’s homing missiles is chasing you and you decide to get rid of them by steering them into the wall. 500 explosions is playing simultaneously causing ears bleeding. With existing implementation explosion sounds will switch very fast, but still interfere with each other in very unpleasant way. Playthrough rejection seems better solution.
Supplemental
I actually did the rejection logic for the first version of audio engine. I would love to get rid of it, since now the BabylonJS native instance tracker is introduced.
But just for the reference, this is how I did it:
export default class AudioManager {
constructor() {
this.queue = [];
this.collector = {};
this.updateFunction = this.update.bind(this);
this.stepId = 0;
this.threshold = 15;
}
push(sound) {
NodeMetadata.write(sound, METADATA_FIELDS.STEP_ID, this.stepId);
this.queue.push(sound);
}
update() {
while (this.queue.length > 0) {
const sound = this.queue.shift();
const incomingStepId = NodeMetadata.read(sound, METADATA_FIELDS.STEP_ID);
if (!this.collector[sound.id] || incomingStepId - this.collector[sound.id] > this.threshold) {
this.collector[sound.id] = incomingStepId;
sound.play();
}
}
this.stepId++;
}
}
So, instead of mySound.play() you need to call myAudioManagerInstance.push(mySound).
Here are two important structures:
this.queuecontains sounds that were requested to play.this.collectorcontains sounds that were allowed to play.
When push method is called the sound is added to the queue. The specific stepId identificator will be added to the sound metadata, stepId is just a frame number.
update function is called on every frame. It gets the sound and checks if such sound already exists in the collector. If no, then just add and play; if yes, check the stepId difference between collector and queue instances of the sound. If it’s less or equal than threshold, reject the queue instance; if it’s bigger than threshold, then update info in the collector and play the sound.
This approach assumes that you always clone the sound, before calling the push, otherwise all entries will share the same metadata.
I think it’s pretty cannonical design pattern, but I don’t know if it has the cannonical name. “Rate-Limited Event Queue”, something like that.