blob: 311da05f29b1986c93417f42d6c10499d58aedaf [file] [log] [blame]
part of sprites;
enum SoundFadeMode {
crossFade,
fadeOutThenPlay,
fadeOutThenFadeIn,
}
enum SoundEventSimultaneousPolicy {
dontPlay,
stopOldest,
}
enum SoundEventMinimumOverlapPolicy {
dontPlay,
delay,
}
class SoundEvent {
SoundEvent(SoundEffect effect) {
effects = [effect];
}
SoundEvent.withList(this.effects);
List<SoundEffect> effects;
double pitchVariance = 0.0;
double volumeVariance = 0.0;
double panVariance = 0.0;
SoundEventSimultaneousPolicy simultaneousLimitPolicy = SoundEventSimultaneousPolicy.stopOldest;
int simultaneousLimit = 0;
SoundEventMinimumOverlapPolicy minimumOverlapPolicy = SoundEventMinimumOverlapPolicy.dontPlay;
double minimumOverlap = 0.0;
}
class _PlayingSoundEvent {
SoundEvent event;
SoundEffectStream stream;
int startTime;
}
SoundManager _sharedSoundManager;
class SoundManager {
static SoundManager sharedInstance() {
if (_sharedSoundManager == null) {
_sharedSoundManager = new SoundManager();
}
return _sharedSoundManager;
}
static void purgeSharedInstance() {
if (_sharedSoundManager == null) return;
_sharedSoundManager = null;
}
SoundManager() {
new Timer.periodic(new Duration(milliseconds:10), _update);
}
Map<SoundEvent, List<_PlayingSoundEvent>> _playingEvents = {};
SoundTrack _backgroundMusicTrack;
SoundEffectPlayer _effectPlayer = SoundEffectPlayer.sharedInstance();
SoundTrackPlayer _trackPlayer = SoundTrackPlayer.sharedInstance();
ActionController actions = new ActionController();
bool enableBackgroundMusic;
bool enableSoundEffects;
int _lastTimeStamp;
void playEvent(SoundEvent evt, [double volume = 1.0, double pitch = 1.0, double pan = 0.0]) {
List<_PlayingSoundEvent> playingList = _playingEvents[evt];
if (playingList == null) playingList = [];
// Check simultaneousLimit
if (evt.simultaneousLimit != 0 && evt.simultaneousLimit >= playingList.length) {
// We have too many sounds playing
if (evt.simultaneousLimitPolicy == SoundEventSimultaneousPolicy.dontPlay) {
// Skip this sound event
return;
} else {
// Stop the oldest sound
_effectPlayer.stop(playingList[0].stream);
}
}
// Check for overlap
int playTime = new DateTime.now().millisecondsSinceEpoch;
if (evt.minimumOverlap != 0.0 && playingList.length > 0) {
int overlap = playTime - playingList.last.startTime;
if (overlap.toDouble() / 1000.0 < evt.minimumOverlap) {
// Sounds are overlapping
if (evt.minimumOverlapPolicy == SoundEventMinimumOverlapPolicy.dontPlay) {
return;
} else {
// TODO: try to play the sound a little bit later
return;
}
}
}
// Create a new entry for the event
_PlayingSoundEvent newPlaying = new _PlayingSoundEvent();
newPlaying.startTime = playTime;
newPlaying.event = evt;
// Pick a sound effect to play
SoundEffect effect = evt.effects.elementAt(randomInt(evt.effects.length));
// Add the entry
playingList.add(newPlaying);
// Play the event
newPlaying.stream = _effectPlayer.play(
effect,
false,
(volume + evt.volumeVariance * randomSignedDouble()).clamp(0.0, 2.0),
(pitch + evt.pitchVariance * randomSignedDouble()).clamp(0.5, 2.0),
(pan + evt.panVariance * randomSignedDouble()).clamp(-1.0, 1.0),
(SoundEffectStream s) {
// Completion callback - remove the entry
playingList.remove(newPlaying);
}
);
}
void stopAllEvents([double fadeDuration]) {
for (List<_PlayingSoundEvent> playingList in _playingEvents) {
for (_PlayingSoundEvent playing in playingList) {
if (fadeDuration > 0.0) {
// Fade out and stop
ActionTween fadeOut = new ActionTween((a) => playing.stream.volume = a, playing.stream.volume, 0.0, fadeDuration);
ActionCallFunction stop = new ActionCallFunction(() { _effectPlayer.stop(playing.stream); });
ActionSequence seq = new ActionSequence([fadeOut, stop]);
actions.run(seq);
}
else {
// Stop right away
_effectPlayer.stop(playing.stream);
}
}
}
}
void playBackgroundMusic(SoundTrack track, [double fadeDuration = 0.0, SoundFadeMode fadeMode = SoundFadeMode.fadeOutThenPlay]) {
double fadeInDuration = 0.0;
double fadeInDelay = 0.0;
double fadeOutDuration = 0.0;
// Calculate durations
if (fadeDuration > 0.0) {
if (fadeMode == SoundFadeMode.crossFade) {
fadeOutDuration = fadeDuration;
fadeInDuration = fadeDuration;
} else if (fadeMode == SoundFadeMode.fadeOutThenPlay) {
fadeOutDuration = fadeDuration;
fadeInDelay = fadeDuration;
} else if (fadeMode == SoundFadeMode.fadeOutThenFadeIn) {
fadeOutDuration = fadeDuration / 2.0;
fadeInDuration = fadeDuration / 2.0;
fadeInDelay = fadeDuration / 2.0;
}
}
if (_backgroundMusicTrack != null) {
// Stop the current track
if (fadeOutDuration == 0.0) {
_trackPlayer.stop(_backgroundMusicTrack);
} else {
ActionTween fadeOut = new ActionTween((a) => _backgroundMusicTrack.volume = a, _backgroundMusicTrack.volume, 0.0, fadeOutDuration);
ActionCallFunction stop = new ActionCallFunction(() { _trackPlayer.stop(_backgroundMusicTrack); });
ActionSequence seq = new ActionSequence([fadeOut, stop]);
actions.run(seq);
}
} else {
fadeInDelay = 0.0;
}
// Fade in new sound
if (fadeInDelay == 0.0) {
_fadeInTrack(track, fadeInDuration);
} else {
ActionDelay delay = new ActionDelay(fadeInDelay);
ActionCallFunction fadeInCall = new ActionCallFunction(() {
_fadeInTrack(track, fadeInDuration);
});
ActionSequence seq = new ActionSequence([delay, fadeInCall]);
actions.run(seq);
}
}
void _fadeInTrack(SoundTrack track, double duration) {
_backgroundMusicTrack = track;
if (duration == 0.0) {
_trackPlayer.play(track);
} else {
_trackPlayer.play(track, true, 0.0);
actions.run(new ActionTween((a) => track.volume = a, 0.0, 1.0, duration));
}
}
void stopBackgroundMusic([double fadeDuration = 0.0]) {
if (fadeDuration == 0.0) {
_trackPlayer.stop(_backgroundMusicTrack);
} else {
ActionTween fadeOut = new ActionTween(
(a) => _backgroundMusicTrack.volume = a,
_backgroundMusicTrack.volume, 0.0, fadeDuration);
ActionCallFunction stopCall = new ActionCallFunction(() {
_trackPlayer.stop(_backgroundMusicTrack);
});
ActionSequence seq = new ActionSequence([fadeOut, stopCall]);
actions.run(seq);
}
_backgroundMusicTrack = null;
}
void _update(Timer timer) {
int delta = 0;
int timestamp = new DateTime.now().millisecondsSinceEpoch;
if (_lastTimeStamp != null) {
delta = timestamp - _lastTimeStamp;
}
_lastTimeStamp = timestamp;
actions.step(delta / 1000.0);
}
}