public void PlayCueUI(Cue cue, int playerIndex, bool useHudStereo) { var parameters = PlayCueParameters.GetParameters(cue, random, GameState.cueStates); // ^^^^ Affects gameplay || Local-only vvvv if (soundRollbackManager != null) { var fpp = new FadePitchPan(1f); if (useHudStereo) // TODO: Consider not using stereo if only one player is in game? { fpp.pan = -0.6f + 0.4f * playerIndex; // NOTE: Not using "audio space" because we don't want normal camera effects for HUD audio } var playLocally = (localPlayerBits & (1 << playerIndex)) != 0; // NOTE: Cheat and use an impossible world position so it (really) can't interfere with in-game sounds #if DEVELOPER soundRollbackManager.PlayCue(Definitions, cue, new Position(-6000 + 4000 * playerIndex, -100000, 0), fpp, parameters, playLocally); #else soundRollbackManager.PlayCueSkipMissingCheck(Definitions, cue, new Position(-6000 + 4000 * playerIndex, -100000, 0), fpp, parameters, playLocally); #endif } }
/// <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>Play a cue in a world-space position (relative to the camera)</summary> public void PlayCueWorld(Cue cue, Actor source) { var parameters = PlayCueParameters.GetParameters(Definitions, cue, random, GameState.cueStates); // ^^^^ Affects gameplay || Local-only vvvv if (soundRollbackManager != null && AudioCamera != null) { var fpp = new FadePitchPan(AudioCamera.WorldToAudio(source.position)); #if DEVELOPER soundRollbackManager.PlayCue(Definitions, cue, source.position, fpp, parameters, playsLocally: true); #else soundRollbackManager.PlayCueSkipMissingCheck(Definitions, cue, source.position, fpp, parameters, playsLocally: true); #endif } }
/// <summary>Play a sound without any position (always plays centred)</summary> public void PlayCueGlobal(Cue cue, Actor source = null) // <- keeping source around, in case it is useful information (will become useful for rollback) { var parameters = PlayCueParameters.GetParameters(Definitions, cue, random, GameState.cueStates); // ^^^^ Affects gameplay || Local-only vvvv if (soundRollbackManager != null) { var fpp = new FadePitchPan(1f); #if DEVELOPER soundRollbackManager.PlayCue(Definitions, cue, null, fpp, parameters, playsLocally: true); #else soundRollbackManager.PlayCueSkipMissingCheck(Definitions, cue, null, fpp, parameters, playsLocally: true); #endif } }
/// <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 = MathHelper.Clamp(fpp.fade * cue.volume, 0, 1); fpp.pitch = MathHelper.Clamp(fpp.pitch + parameters.cuePitch, -1, 1); fpp.pan = MathHelper.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 (int i = 0; i < cue.SoundCount; i++) { AddAndStartPlaying(definitions.GetSound(cue, i), fpp, loop); } } break; case CueType.Serial: { // Build the queue for the cue: int queueHead = -1; for (int i = cue.SoundCount - 1; i >= 1; i--) { int 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); }
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++; }
public bool Play(FadePitchPan fpp) { return(soundEffect != null && soundEffect.Play(fpp.fade * _sfxVolume, fpp.pitch, fpp.pan)); }
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); } } }
/// <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); }