Adds SoundManager class
diff --git a/examples/game/lib/sound.dart b/examples/game/lib/sound.dart index e502f6b..5e50e7f 100644 --- a/examples/game/lib/sound.dart +++ b/examples/game/lib/sound.dart
@@ -149,6 +149,7 @@ SoundTrackBufferingCallback onBufferingUpdate; bool loop; double time; + double volume; } SoundTrackPlayer _sharedSoundTrackPlayer;
diff --git a/examples/game/lib/sound_manager.dart b/examples/game/lib/sound_manager.dart new file mode 100644 index 0000000..90c03a9 --- /dev/null +++ b/examples/game/lib/sound_manager.dart
@@ -0,0 +1,238 @@ +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._running = false; + _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; + + bool _running = true; + 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); + } +}
diff --git a/examples/game/lib/sprites.dart b/examples/game/lib/sprites.dart index 155dcc2..6522290 100644 --- a/examples/game/lib/sprites.dart +++ b/examples/game/lib/sprites.dart
@@ -31,6 +31,7 @@ part 'node_with_size.dart'; part 'particle_system.dart'; part 'sound.dart'; +part 'sound_manager.dart'; part 'sprite.dart'; part 'spritesheet.dart'; part 'sprite_box.dart';