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';