sound_manager.dart 6.94 KB
Newer Older
1
part of skysprites;
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

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