| 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); |
| } |
| } |