/// <returns>Returns true if this ambient sound is playable</returns> public static bool GetPlaybackInfoFor(AnimationSet animationSet, Position position, bool facingLeft, int radius, float volume, float pitch, float pan, Camera camera, IGameState gameState, int localPlayerBits, out FadePitchPan fadePitchPan) { // // Global ambient audio // if (radius < 0) { fadePitchPan = new FadePitchPan(1f); return(true); } // // Nominal audio position: // fadePitchPan = new FadePitchPan(camera.WorldToAudio(position)); fadePitchPan.fade *= volume; fadePitchPan.pitch *= pitch; fadePitchPan.pan *= pan; if (fadePitchPan.fade < 0.001f) // Far off-camera { fadePitchPan.fade = 0; return(false); } // // Distance to a listening player: // if (localPlayerBits == 0) // No one to listen { fadePitchPan.fade = 0; return(false); } int distanceSquared = GetDistanceSquaredToLocalPlayer(animationSet, position, facingLeft, gameState, localPlayerBits); if (distanceSquared > radius * radius) { fadePitchPan.fade = 0; return(false); } // // Modulate by listening player distance and apply settings: // // Trying linear fade-out here float listenerFade = MathHelper.Clamp(1f - (float)Math.Sqrt(distanceSquared) / (float)radius, 0f, 1f); fadePitchPan.fade *= listenerFade; return(true); }
public static void TriedToPlayMissingCue(FadePitchPan fpp) { SafeSoundEffect sound; lock (lockObject) sound = missingSoundEffect; if (sound != null) { sound.Play(fpp.fade, fpp.pitch, fpp.pan); } }
/// <summary>Call this only when PlayCueParameters have been validated</summary> public static void PlayCueSkipMissingCheck(IAudioDefinitions definitions, Cue cue, PlayCueParameters parameters, FadePitchPan fpp, bool loop = false) { fpp.fade = AudioMath.Clamp(fpp.fade * cue.volume, 0, 1); fpp.pitch = AudioMath.Clamp(fpp.pitch + parameters.cuePitch, -1, 1); fpp.pan = AudioMath.Clamp(fpp.pan + cue.pan, -1, 1); if (fpp.fade < 0.01f) { return; // too quiet to care about } if (cue.SoundCount == 0) { return; // <- nothing to play } lock (lockObject) { switch (cue.type) { case CueType.Parallel: { for (var i = 0; i < cue.SoundCount; i++) { AddAndStartPlaying(definitions.GetSound(cue, i), fpp, loop); } } break; case CueType.Serial: { // Build the queue for the cue: var queueHead = -1; for (var i = cue.SoundCount - 1; i >= 1; i--) { var q = AllocateQueuedSound(); queuedSounds[q].sound = definitions.GetSound(cue, i); queuedSounds[q].next = queueHead; queueHead = q; } AddAndStartPlaying(definitions.GetSound(cue, 0), fpp, loop, queueHead); } break; default: { AddAndStartPlaying(definitions.GetSound(cue, parameters.soundIndex), fpp, loop); } break; } } }
public static void PlayCue(IAudioDefinitions definitions, Cue cue, PlayCueParameters parameters, FadePitchPan fpp, bool loop = false) { if (parameters.soundIndex == PlayCueParameters.MISSING_CUE) { MissingAudio.TriedToPlayMissingCue(fpp); return; } if (parameters.soundIndex < 0) { return; } PlayCueSkipMissingCheck(definitions, cue, parameters, fpp, loop); }
public void PlayCueSkipMissingCheck(IAudioDefinitions definitions, Cue cue, Position?worldPosition, FadePitchPan fpp, PlayCueParameters parameters, bool playsLocally) { if (parameters.soundIndex < 0) { return; } if (!playsLocally || !AudioDevice.Available) { return; // <- nothing to do! } if (doingPrediction) // Re-prediction following rollback { if (activeFrame >= liveFrame - DontCareLimit) // <- new enough that we could still be tracking it { if (!TryKillCueExact(cue, activeFrame, worldPosition)) { if (activeFrame >= liveFrame - MaximumSoundShift) // <- new enough that we may play it { PendingCue pending; pending.cue = cue; pending.parameters = parameters; pending.frame = activeFrame; pending.position = worldPosition; pending.fpp = fpp; pendingCues.Add(pending); } } } } else // Standard playback { Debug.Assert(activeFrame == liveFrame); if (!rollbackAware || !TryKillCueFuzzy(cue, activeFrame, worldPosition)) { if (!doingStartupPrediction) { SoundEffectManager.PlayCueSkipMissingCheck(definitions, cue, parameters, fpp); } AddLiveCueNow(cue, worldPosition); } } }
private static void AddAndStartPlaying(SafeSoundEffect sound, FadePitchPan fpp, bool loop = false, int queue = -1) { Debug.Assert(playingSoundCount <= playingSounds.Length); if (playingSoundCount == playingSounds.Length) { Array.Resize(ref playingSounds, playingSounds.Length * 2); } Debug.Assert(playingSounds[playingSoundCount].sound == null); // <- got cleared properly Debug.Assert(playingSounds[playingSoundCount].instance == null); // <- got cleared properly Debug.Assert(playingSounds[playingSoundCount].frameCount == 0); // <- got cleared properly // If we are about to play multiple identical sounds at about the same time, stop them from overlapping: float quashFade = 1f; for (int i = playingSoundCount - 1; i >= 0; i--) { if (playingSounds[i].frameCount >= 3) { break; // <- Reaching sounds that are too old } if (ReferenceEquals(playingSounds[i].sound, sound)) { quashFade -= (1f - ((float)playingSounds[i].frameCount / 3f)); } } // TODO: The following is ugly, because it kills sequential sounds (but odds are they would be killed anyway - and because we just use `fpp.fade`, below, they'd get killed anyway) // If a sound would be quashed completely, just don't play it -- this is required because otherwise the quashed sounds would be taking up simulated channels if (quashFade < 0.1f) { while (queue != -1) // Don't leak the queue, if any { queue = FreeQueuedSound(queue); } return; } // TODO: This is ugly because sequential sounds will inherit this fade level fpp.fade *= MathHelper.Clamp(quashFade, 0f, 1f); if (loop) { playingSounds[playingSoundCount].instance = sound.CreateInstance(); } else { playingSounds[playingSoundCount].instance = sound.SoundEffectManager_GetInstance(); } if (playingSounds[playingSoundCount].instance == null) { while (queue != -1) // Don't leak the queue, if any { queue = FreeQueuedSound(queue); } return; // Failed to create sound instance... oh well. } Debug.Assert(playingSounds[playingSoundCount].instance.IsLooped == false); // <- instance was properly cleared if (loop) // <- Cannot set on used instances (even to the same value) { playingSounds[playingSoundCount].instance.IsLooped = true; } playingSounds[playingSoundCount].sound = sound; playingSounds[playingSoundCount].fpp = fpp; playingSounds[playingSoundCount].queue = queue; playingSounds[playingSoundCount].linkToNext = true; // <- all sounds on a given frame get linked! playingSounds[playingSoundCount].fade = 1f; // <- NOTE: assumed by channel ducking code playingSounds[playingSoundCount].frameCount = 0; fpp.ApplyTo(playingSounds[playingSoundCount].instance, SafeSoundEffect.SoundEffectVolume); playingSounds[playingSoundCount].instance.Play(); playingSoundCount++; }
/// <param name="random">IMPORTANT: This may be part of the game state</param> /// <param name="cueStates">IMPORTANT: This may be part of the game state</param> /// <param name="playLocally"> /// True if this sound should be played, false if the sound is for a remote player (not for us). /// Allows for local-only UI sounds. /// </param> public void PlayCue(IAudioDefinitions definitions, Cue cue, Position?worldPosition, FadePitchPan fpp, PlayCueParameters parameters, bool playsLocally) { if (parameters.soundIndex == PlayCueParameters.MISSING_CUE) { if (!doingPrediction ) // <- poor man's "first time simulated" (Missing music doesn't get rollback-aware sound handling) { MissingAudio.TriedToPlayMissingCue(fpp); } return; } PlayCueSkipMissingCheck(definitions, cue, worldPosition, fpp, parameters, playsLocally); }
public bool Play(FadePitchPan fpp) { return(inner != null && inner.Play(fpp.fade * SoundEffectVolume, fpp.pitch, fpp.pan)); }
public bool Play(FadePitchPan fpp) { return(soundEffect != null && soundEffect.Play(fpp.fade * _sfxVolume, fpp.pitch, fpp.pan)); }