///|
/// Outcome of a cue request. Playback errors remain errors, not missing cues.
pub(all) enum CuePlayback {
Played
Suppressed
Muted
Missing
} derive(Eq, Debug)
///|
pub impl Show for CuePlayback with fn output(self, logger) {
logger.write_object(Repr(self))
}
///|
/// Owns one non-overlapping voice per game-defined key in an existing mixer.
/// The game retains responsibility for event mapping, asset loading and gains.
pub struct SoundBank[Key] {
priv context : MixerAudioContext
priv entries : Array[(Key, PlayerId)]
priv mut paused : Bool
priv mut paused_players : Array[PlayerId]
priv mut muted : Bool
}
///|
pub fn[Key] SoundBank::new(context : MixerAudioContext) -> SoundBank[Key] {
{ context, entries: [], muted: false, paused: false, paused_players: [], }
}
///|
pub fn[Key : Eq] SoundBank::player(
self : SoundBank[Key],
cue : Key,
) -> PlayerId? {
for (key, player) in self.entries {
if key == cue {
return Some(player)
}
}
None
}
///|
/// Replacing a cue disposes its previous voice, including an actively playing one.
pub fn[Key : Eq] SoundBank::register(
self : SoundBank[Key],
cue : Key,
clip : AudioClip,
volume? : Double = 1.0,
) -> Unit raise {
let player = self.context.create_player(clip)
self.context.set_volume(player, volume)
for i in 0.. {
id.value != old.value
})
self.context.dispose(old)
self.entries[i] = (cue, player)
return
}
}
self.entries.push((cue, player))
}
///|
/// Finish the previous cue by default. Explicit restart is useful for UI sounds.
pub fn[Key : Eq] SoundBank::play(
self : SoundBank[Key],
cue : Key,
restart? : Bool = false,
) -> CuePlayback raise {
if self.muted {
return Muted
}
if self.paused {
return Suppressed
}
match self.player(cue) {
None => Missing
Some(player) => {
match self.context.state(player) {
Playing => if !restart { return Suppressed }
_ => ()
}
self.context.seek(player, 0.0)
self.context.play(player)
Played
}
}
}
///|
pub fn[Key] SoundBank::set_muted(self : SoundBank[Key], muted : Bool) -> Unit {
self.muted = muted
if muted {
self.paused_players.clear()
for (_, player) in self.entries {
self.context.stop(player) catch {
_ => ()
}
}
}
}
///|
pub fn[Key] SoundBank::is_muted(self : SoundBank[Key]) -> Bool {
self.muted
}
///|
pub fn[Key] SoundBank::loaded_count(self : SoundBank[Key]) -> Int {
self.entries.length()
}
///|
pub fn[Key] SoundBank::playing_count(self : SoundBank[Key]) -> Int {
let mut count = 0
for (_, player) in self.entries {
match self.context.state(player) {
Playing => count += 1
_ => ()
}
}
count
}
///|
pub fn[Key] SoundBank::clear(self : SoundBank[Key]) -> Unit {
for (_, player) in self.entries {
self.context.dispose(player)
}
self.entries.clear()
self.paused_players.clear()
}
///|
/// Freeze cue positions without changing the user's mute preference.
/// Only voices playing at entry resume; replaced and muted cues stay stopped.
pub fn[Key] SoundBank::set_paused(self : SoundBank[Key], paused : Bool) -> Unit {
if self.paused == paused {
return
}
self.paused = paused
if paused {
for (_, player) in self.entries {
if self.context.state(player) is Playing {
self.context.pause(player) catch {
_ => continue
}
self.paused_players.push(player)
}
}
} else {
if !self.muted {
for player in self.paused_players {
self.context.play(player) catch {
_ => ()
}
}
}
self.paused_players.clear()
}
}
///|
pub fn[Key] SoundBank::is_paused(self : SoundBank[Key]) -> Bool {
self.paused
}