/// <summary> /// Keep an event in the "ActiveEvents" list for the amount of time we think it will be playing, plus the instance buffer. /// This is mostly done for instance limiting purposes. /// </summary> /// <param name="activeEvent">The persistent reference to the event as long as it is playing.</param> /// <returns>The coroutine.</returns> private IEnumerator RecordEventInstanceCoroutine(ActiveEvent activeEvent) { // Unity has no callback for an audioclip ending, so we have to estimate it ahead of time. // Changing the pitch during playback will alter actual playback time. ActiveEvents.Add(activeEvent); // Only return active time if sound is not Looping/continuous. if (activeEvent.ActiveTime > 0) { yield return(new WaitForSeconds(activeEvent.ActiveTime)); // Mark this event so it no longer counts against the instance limit. activeEvent.IsActiveTimeComplete = true; // Since the ActiveTime estimate may not be enough time to complete the clip (due to pitch changes during playback, or a negative instanceBuffer value, for example) // wait here until it is finished, so that we don't cut off the end. if (activeEvent.IsPlaying) { yield return(null); } } // Otherwise, continue at next frame. else { yield return(null); } if (activeEvent.ActiveTime != InfiniteLoop) { RemoveEventInstance(activeEvent); } }
public override IEnumerator PlayAsync(ActiveEvent activeEvent) { BeginPlay(); if (sound == null) { Debug.LogErrorFormat(this, "Trying to play empty sound for event \"{0}\".", activeEvent.audioEvent.name); yield break; } this.audioSource = activeEvent.CurrentAudioSource; if (this.CurrentDelay > 0.0f) { yield return(new WaitForSeconds(this.CurrentDelay)); } if (!activeEvent.StopRequested) { if (PlaySimultaneous) { audioSource.PlayOneShot(sound); } else { audioSource.PlayClip(sound, Loop); } } }
/// <summary> /// Play a single clip on an AudioSource; if Looping forever, return InfiniteLoop for the event time. /// </summary> /// <param name="audioClip">The audio clip to play.</param> /// <param name="emitter">The emitter to use.</param> /// <param name="activeEvent">The persistent reference to the event as long as it is playing.</param> /// <returns>The amount of delay, if any, we are waiting before playing the clip. A Looping clip will always return InfiniteLoop.</returns> private float PlayClipAndGetTime(UAudioClip audioClip, AudioSource emitter, ActiveEvent activeEvent) { if (audioClip.DelayCenter == 0) { emitter.PlayClip(audioClip.Sound, audioClip.Looping); if (audioClip.Looping) { return(InfiniteLoop); } return(0); } else { float rndDelay = Random.Range(audioClip.DelayCenter - audioClip.DelayRandomization, audioClip.DelayCenter + audioClip.DelayRandomization); StartCoroutine(PlayClipDelayedCoroutine(audioClip, emitter, rndDelay, activeEvent)); if (audioClip.Looping) { return(InfiniteLoop); } return(rndDelay); } }
/// <summary> /// Coroutine for fading out an AudioSource, and stopping the event once fade is complete. /// </summary> /// <param name="activeEvent">The persistent reference to the event as long as it is playing.</param> /// <param name="fadeTime">The amount of time, in seconds, to completely fade out the sound.</param> /// <returns>The coroutine.</returns> protected IEnumerator StopEventWithFadeCoroutine(ActiveEvent activeEvent, float fadeTime) { if (activeEvent.IsStoppable) { activeEvent.IsStoppable = false; activeEvent.VolDest = 0f; activeEvent.AltVolDest = 0f; activeEvent.CurrentFade = fadeTime; yield return(new WaitForSeconds(fadeTime)); if (activeEvent.PrimarySource != null) { activeEvent.PrimarySource.Stop(); } if (activeEvent.SecondarySource != null) { activeEvent.SecondarySource.Stop(); } activeEvent.CancelEvent = true; RemoveEventInstance(activeEvent); } }
/// <summary> /// Get an available AudioSource. /// </summary> /// <param name="emitter">The audio emitter on which the AudioSource is desired.</param> /// <param name="currentEvent">The current audio event.</param> /// <returns></returns> private AudioSource GetUnusedAudioSource(GameObject emitter, ActiveEvent currentEvent = null) { // Get or create valid AudioSource. AudioSourcesReference sourcesReference = emitter.GetComponent <AudioSourcesReference>(); if (sourcesReference != null) { List <AudioSource> sources = sourcesReference.AudioSources; for (int s = 0; s < sources.Count; s++) { if (!sources[s].isPlaying && !sources[s].enabled) { if (currentEvent == null) { return(sources[s]); } else if (sources[s] != currentEvent.PrimarySource) { return(sources[s]); } } } } else { sourcesReference = emitter.AddComponent <AudioSourcesReference>(); } return(sourcesReference.AddNewAudioSource()); }
/// <summary> /// Play one sound from a container based on container behavior. /// </summary> /// <param name="currentContainer"></param> /// <param name="activeEvent"></param> /// <returns>The estimated ActiveTime for the clip, or InfiniteLoop if the container and/or clip are set to loop.</returns> private float PlaySingleClip(AudioContainer currentContainer, ActiveEvent activeEvent) { float tempDelay = 0; if (currentContainer.ContainerType == AudioContainerType.Random) { currentContainer.CurrentClip = Random.Range(0, currentContainer.Sounds.Length); } UAudioClip currentClip = currentContainer.Sounds[currentContainer.CurrentClip]; // Trigger sound and save the delay (in seconds) to add to the total amount of time the event will be considered active. tempDelay = PlayClipAndGetTime(currentClip, activeEvent.PrimarySource, activeEvent); // Ready the next clip in the series if sequence container. if (currentContainer.ContainerType == AudioContainerType.Sequence) { currentContainer.CurrentClip++; if (currentContainer.CurrentClip >= currentContainer.Sounds.Length) { currentContainer.CurrentClip = 0; } } // Return active time based on Looping or clip time. return(GetActiveTimeEstimate(currentClip, activeEvent, tempDelay)); }
/// <summary> /// Play all clips in container simultaneously /// </summary> private float PlaySimultaneousClips(AudioContainer currentContainer, ActiveEvent activeEvent) { float tempDelay = 0; float finalActiveTime = 0f; if (currentContainer.looping) { finalActiveTime = InfiniteLoop; } for (int i = 0; i < currentContainer.sounds.Length; i++) { tempDelay = PlayClipAndGetTime(currentContainer.sounds[i], activeEvent.PrimarySource, activeEvent); if (finalActiveTime != InfiniteLoop) { float estimatedActiveTimeNeeded = GetActiveTimeEstimate(currentContainer.sounds[i], activeEvent, tempDelay); if (estimatedActiveTimeNeeded == InfiniteLoop || estimatedActiveTimeNeeded > finalActiveTime) { finalActiveTime = estimatedActiveTimeNeeded; } } } return(finalActiveTime); }
/// <summary> /// Stops an AudioEvent. /// </summary> /// <param name="eventName">The name associated with the AudioEvent.</param> /// <param name="emitter">The GameObject on which the AudioEvent will stopped.</param> /// <param name="fadeTime">The amount of time in seconds to completely fade out the sound.</param> public void StopEvent(string eventName, GameObject emitter = null, float fadeOutTime = 0f) { emitter = ApplyAudioEmitterTransform(emitter); if (emitter == null) { //if emitter is null, use the uaudiomanager gameobject(2dsound) emitter = gameObject; } for (int i = activeEvents.Count - 1; i >= 0; i--) { ActiveEvent activeEvent = activeEvents[i]; if (activeEvent.audioEvent.Name == eventName && activeEvent.AudioEmitter == emitter) { //if there's no fade specified, use the fade stored in the event if (fadeOutTime > 0f) { StartCoroutine(StopEventWithFadeCoroutine(activeEvent, fadeOutTime)); } else { StartCoroutine(StopEventWithFadeCoroutine(activeEvent, activeEvents[i].audioEvent.FadeOutTime)); } } } }
/// <summary> /// Stops an AudioEvent. /// </summary> /// <param name="eventName">The name associated with the AudioEvent.</param> /// <param name="emitter">The GameObject on which the AudioEvent will stopped.</param> /// <param name="fadeTimeOverride">The amount of time in seconds to completely fade out the sound. If not null, this will override the fade-out time specified in the event.</param> public void StopEvent(string eventName, GameObject emitter, float?fadeTimeOverride) { emitter = ApplyAudioEmitterTransform(emitter); if (emitter == null) { return; } for (int i = activeEvents.Count - 1; i >= 0; i--) { ActiveEvent activeEvent = activeEvents[i]; if (activeEvent.audioEvent.name == eventName && activeEvent.AudioEmitter == emitter) { // A fadeTimeOverride of 0 will fall through to the else. if ((fadeTimeOverride.HasValue && fadeTimeOverride > 0.0f) || (!fadeTimeOverride.HasValue && activeEvent.audioEvent.fadeOutTime > 0.0f)) { // If a fadeTime value was provided, then override the fade-out time from the event. float fadeTimeValue = fadeTimeOverride.HasValue ? fadeTimeOverride.Value : activeEvent.audioEvent.fadeOutTime; activeEvent.StopEvent(fadeTimeValue); } else { StopEvent(activeEvent); } } } }
/// <summary> /// Plays an AudioEvent. /// </summary> /// <param name="audioEvent">The AudioEvent to play.</param> /// <param name="emitter">The GameObject on which the AudioEvent is to be played.</param> /// <param name="primarySource">The AudioSource component to use as the primary source for the event.</param> /// <param name="secondarySource">The AudioSource component to use as the secondary source for the event.</param> /// <param name="messageOnAudioEnd">The Message to Send to the GameObject when the sound has finished playing.</param> private void PlayEvent(AudioEvent audioEvent, GameObject emitter, AudioSource primarySource, AudioSource secondarySource, string messageOnAudioEnd = null) { ActiveEvent tempEvent = ActiveEventsGameObject.AddComponent <ActiveEvent>(); tempEvent.Initialize(audioEvent, emitter, primarySource, secondarySource); activeEvents.Add(tempEvent); // The base class owns this event once we pass it to PlayContainer, and may dispose it if it cannot be played. Action onPlayCompletedCallback = null; onPlayCompletedCallback = () => { RemoveEventInstance(tempEvent); tempEvent.OnPlayCompleted -= onPlayCompletedCallback; // Send message notifying user that sound is complete if (!string.IsNullOrEmpty(messageOnAudioEnd)) { tempEvent.AudioEmitter.SendMessage(messageOnAudioEnd); } }; tempEvent.OnPlayCompleted += onPlayCompletedCallback; tempEvent.StartEvent(); }
/// <summary> /// Repeatedly trigger the one-off container based on the loop time. /// </summary> private IEnumerator PlayLoopingOneOffContainerCoroutine(ActiveEvent activeEvent) { int?currentIndex = null; while (!activeEvent.StopRequested) { UPlayable currentPlayable = null; if (!this.IsSimultaneous) { currentIndex = this.GenerateNextClipIndex(currentIndex); currentPlayable = this.sounds[currentIndex.Value]; } // Start coroutine (don't yield return) so that the wait time is accurate, since length includes delay. StartCoroutine(PlayOneOffContainerCoroutine(activeEvent, currentPlayable)); float eventLoopTime = loopTime; // Protect against containers looping every frame by defaulting to the length of the audio clip. if (eventLoopTime == 0) { if (!this.IsSimultaneous) { eventLoopTime = currentPlayable.GetLength(); } else { eventLoopTime = this.GetLength(); } } yield return(new WaitForSeconds(eventLoopTime)); } }
/// <summary> /// Play a non-continuous container. /// </summary> private float PlayOneOffContainer(ActiveEvent activeEvent) { AudioContainer currentContainer = activeEvent.audioEvent.container; // Fading or looping overrides immediate volume settings. if (activeEvent.audioEvent.fadeInTime == 0 && !activeEvent.audioEvent.container.looping) { activeEvent.volDest = activeEvent.PrimarySource.volume; } // Simultaneous sounds. float clipTime = 0; if (currentContainer.containerType == AudioContainerType.Simultaneous) { clipTime = PlaySimultaneousClips(currentContainer, activeEvent); } // Sequential and Random sounds. else { clipTime = PlaySingleClip(currentContainer, activeEvent); } activeEvent.activeTime = clipTime; return(clipTime); }
// Populate an array of the active events, and add it to the timeline list of all captured audio frames. private void CollectProfilerEvents(ProfilerEvent[] currentEvents) { List <ActiveEvent> activeEvents = UAudioManager.Instance.ProfilerEvents; currentEvents = new ProfilerEvent[activeEvents.Count]; for (int i = 0; i < currentEvents.Length; i++) { ActiveEvent currentEvent = activeEvents[i]; ProfilerEvent tempEvent = new ProfilerEvent(); tempEvent.EventName = currentEvent.audioEvent.name; tempEvent.EmitterName = currentEvent.AudioEmitter.name; // The bus might be null, Unity defaults to Editor-hidden master bus. if (currentEvent.audioEvent.bus == null) { tempEvent.BusName = "-MasterBus-"; } else { tempEvent.BusName = currentEvent.audioEvent.bus.name; } currentEvents[i] = tempEvent; } this.eventTimeline.Add(currentEvents); // Trim the first event if we have exceeded the maximum stored frames. if (this.eventTimeline.Count > MaxFrames) { this.eventTimeline.RemoveAt(0); } this.currentFrame = this.eventTimeline.Count - 1; }
private IEnumerator PlayClipCoroutine(UPlayable playable, ActiveEvent activeEvent) { if (this.IsSimultaneous || this.PlaySimultaneous) { playable.PlaySimultaneous = true; } yield return(playable.PlayAsync(activeEvent)); }
/// <summary> /// Plays an AudioEvent. /// </summary> /// <param name="audioEvent">The AudioEvent to play.</param> /// <param name="emitter">The GameObject on which the AudioEvent is to be played.</param> /// <param name="primarySource">The AudioSource component to use as the primary source for the event.</param> /// <param name="secondarySource">The AudioSource component to use as the secondary source for the event.</param> private void PlayEvent(AudioEvent audioEvent, GameObject emitter, AudioSource primarySource, AudioSource secondarySource) { ActiveEvent tempEvent = new ActiveEvent(audioEvent, emitter, primarySource, secondarySource); // The base class owns this event once we pass it to PlayContainer, and may dispose it if it cannot be played. PlayContainer(tempEvent); }
/// <summary> /// Begin playing a non-continuous container, loop if applicable. /// </summary> private IEnumerator StartOneOffEventCoroutine(ActiveEvent activeEvent, UPlayable playable) { if (Loop) { yield return(PlayLoopingOneOffContainerCoroutine(activeEvent)); } else { yield return(PlayOneOffContainerCoroutine(activeEvent, playable)); } }
/// <summary> /// Remove event from the currently active events. /// </summary> /// <param Name="activeEvent">The persistent reference to the event as long as it is playing.</param> private void RemoveEventInstance(ActiveEvent activeEvent) { ActiveEvents.Remove(activeEvent); // Send message notifying user that sound is complete if (!string.IsNullOrEmpty(activeEvent.MessageOnAudioEnd)) { activeEvent.AudioEmitter.SendMessage(activeEvent.MessageOnAudioEnd); } activeEvent.Dispose(); }
/// <summary> /// Stop event by gameObject. /// </summary> /// <param name="eventName"></param> /// <param name="gameObjectToStop"></param> /// <param name="fadeOutTime"></param> public void StopEventsOnGameObject(string eventName, GameObject gameObjectToStop, float fadeOutTime = 0f) { for (int i = activeEvents.Count - 1; i >= 0; i--) { ActiveEvent activeEvent = activeEvents[i]; if (activeEvent.AudioEmitter == gameObjectToStop) { StopEvent(activeEvent.audioEvent.Name, gameObjectToStop, fadeOutTime); } } }
/// <summary> /// Stops the first (oldest) instance of an event with the matching name /// </summary> /// <param name="eventName">The name associated with the AudioEvent to stop.</param> private void KillOldestInstance(string eventName) { for (int i = 0; i < activeEvents.Count; i++) { ActiveEvent tempEvent = activeEvents[i]; if (tempEvent.audioEvent.name == eventName) { StopEvent(tempEvent); return; } } }
/// <summary> /// Calculates the estimated active time for an ActiveEvent playing the given clip. /// </summary> /// <param Name="audioClip">The clip being played.</param> /// <param Name="activeEvent">The event being played.</param> /// <param Name="additionalDelay">The delay before playing in seconds.</param> /// <returns>The estimated active time of the event based on Looping or clip time. If Looping, this will return InfiniteLoop.</returns> private static float GetActiveTimeEstimate(UAudioClip audioClip, ActiveEvent activeEvent, float additionalDelay) { if (audioClip.Looping || activeEvent.AudioEvent.Container.Looping || additionalDelay == InfiniteLoop) { return(InfiniteLoop); } else { float pitchAdjustedClipLength = activeEvent.PrimarySource.pitch != 0 ? (audioClip.Sound.length / activeEvent.PrimarySource.pitch) : 0; // Restrict non-Looping ActiveTime values to be non-negative. return(Mathf.Max(0.0f, pitchAdjustedClipLength + activeEvent.AudioEvent.InstanceTimeBuffer + additionalDelay)); } }
/// <summary> /// Begin playing a non-continuous container, loop if applicable. /// </summary> private void StartOneOffEvent(ActiveEvent activeEvent) { if (activeEvent.AudioEvent.Container.Looping) { StartCoroutine(PlayLoopingOneOffContainerCoroutine(activeEvent)); activeEvent.ActiveTime = InfiniteLoop; } else { PlayOneOffContainer(activeEvent); } StartCoroutine(RecordEventInstanceCoroutine(activeEvent)); }
/// <summary> /// Stop audio sources in an event, and clean up instance references. /// </summary> /// <param Name="activeEvent">The persistent reference to the event as long as it is playing.</param> protected void StopEvent(ActiveEvent activeEvent) { if (activeEvent.PrimarySource != null) { activeEvent.PrimarySource.Stop(); } if (activeEvent.SecondarySource != null) { activeEvent.SecondarySource.Stop(); } activeEvent.CancelEvent = true; RemoveEventInstance(activeEvent); }
/// <summary> /// Repeatedly trigger the one-off container based on the loop time. /// </summary> private IEnumerator PlayLoopingOneOffContainerCoroutine(ActiveEvent activeEvent) { while (!activeEvent.CancelEvent) { float tempLoopTime = PlayOneOffContainer(activeEvent); float eventLoopTime = activeEvent.AudioEvent.Container.LoopTime; // Protect against containers Looping every frame by defaulting to the length of the audio clip. if (eventLoopTime != 0) { tempLoopTime = eventLoopTime; } yield return(new WaitForSeconds(tempLoopTime)); } }
/// <summary> /// Sets the pitch value on active AudioEvents. /// </summary> /// <param name="eventName">The name associated with the AudioEvents.</param> /// <param name="newPitch">The value to set the pitch, between 0 (exclusive) and 3 (inclusive).</param> public void SetPitch(string eventName, float newPitch) { if (newPitch <= 0 || newPitch > 3) { Debug.LogErrorFormat(this, "Invalid pitch {0} set for event \"{1}\"", newPitch, eventName); return; } for (int i = activeEvents.Count - 1; i >= 0; i--) { ActiveEvent activeEvent = activeEvents[i]; if (activeEvent.audioEvent.name == eventName) { activeEvent.SetPitch(newPitch); } } }
public void ApplyValues(ActiveEvent activeEvent) { switch (audioProperty) { case RTPCProperty.Volume: activeEvent.volDest = Evaluate(); activeEvent.currentFade = smoothingTime; break; case RTPCProperty.Pitch: activeEvent.SetPitch(Evaluate()); break; default: throw new ArgumentOutOfRangeException(); } }
/// <summary> /// Determine which rules to follow for container playback, and begin the appropriate function. /// </summary> public override IEnumerator PlayAsync(ActiveEvent activeEvent) { BeginPlay(); if (sounds.Length == 0) { Debug.LogErrorFormat(this, "Trying to play container \"{0}\" with no clips.", this); yield break; } // We store the playable before the delay, so that we have the correct one in the case where another instance (without delay) was started. UPlayable currentPlayable = null; if (this.currentClipIndex.HasValue) { currentPlayable = CurrentPlayable; } if (this.CurrentDelay > 0.0f) { yield return(new WaitForSeconds(this.CurrentDelay)); } if (!activeEvent.StopRequested) { switch (containerType) { case AudioContainerType.Random: case AudioContainerType.Simultaneous: case AudioContainerType.Sequence: yield return(StartOneOffEventCoroutine(activeEvent, currentPlayable)); break; case AudioContainerType.ContinuousSequence: case AudioContainerType.ContinuousRandom: yield return(PlayContinuousContainerCoroutine(activeEvent)); break; default: Debug.LogErrorFormat(this, "Trying to play container \"{0}\" with an unknown AudioContainerType \"{1}\".", this, containerType); yield break; } } }
/// <summary> /// Play a non-continuous container. /// </summary> private IEnumerator PlayOneOffContainerCoroutine(ActiveEvent activeEvent, UPlayable playable) { // Simultaneous sounds. if (this.IsSimultaneous) { foreach (UPlayable sound in sounds) { // We start coroutines here. Since we want to all the sounds to start playing at the same time. StartCoroutine(PlayClipCoroutine(sound, activeEvent)); } } // Sequential and Random sounds. else { yield return(PlayClipCoroutine(playable, activeEvent)); } }
/// <summary> /// Stops an AudioEvent. /// </summary> /// <param name="eventName">The name associated with the AudioEvent.</param> /// <param name="emitter">The GameObject on which the AudioEvent will stopped.</param> /// <param name="fadeTime">The amount of time in seconds to completely fade out the sound.</param> public void StopEvent(string eventName, GameObject emitter, float fadeTime) { emitter = ApplyAudioEmitterTransform(emitter); if (emitter == null) { return; } for (int i = activeEvents.Count - 1; i >= 0; i--) { ActiveEvent activeEvent = activeEvents[i]; if (activeEvent.audioEvent.name == eventName && activeEvent.AudioEmitter == emitter) { StartCoroutine(StopEventWithFadeCoroutine(activeEvent, fadeTime)); } } }
/// <summary> /// Stops all events by name. /// </summary> /// <param name="eventName">The name associated with the AudioEvent.</param> /// <param name="fadeOutTime">The amount of time in seconds to completely fade out the sound.</param> public void StopAllEvents(string eventName, GameObject emitter = null, float fadeOutTime = 0f) { for (int i = activeEvents.Count - 1; i >= 0; i--) { ActiveEvent activeEvent = activeEvents[i]; if (activeEvent.audioEvent.Name == eventName) { if (fadeOutTime > 0) { StartCoroutine(StopEventWithFadeCoroutine(activeEvent, fadeOutTime)); } else { StartCoroutine(StopEventWithFadeCoroutine(activeEvent, activeEvent.audioEvent.FadeOutTime)); } } } }
/// <summary> /// Determine which rules to follow for container playback, and begin the appropriate function. /// </summary> /// <param Name="activeEvent">The event to play.</param> protected void PlayContainer(ActiveEvent activeEvent) { if (activeEvent.AudioEvent.Container.Sounds.Length == 0) { Debug.LogErrorFormat(this, "Trying to play container \"{0}\" with no clips.", activeEvent.AudioEvent.Container); // Clean up the ActiveEvent before we discard it, so it will release its AudioSource(s). activeEvent.Dispose(); return; } switch (activeEvent.AudioEvent.Container.ContainerType) { case AudioContainerType.Random: StartOneOffEvent(activeEvent); break; case AudioContainerType.Simultaneous: StartOneOffEvent(activeEvent); break; case AudioContainerType.Sequence: StartOneOffEvent(activeEvent); break; case AudioContainerType.ContinuousSequence: PlayContinuousSequenceContainer(activeEvent.AudioEvent.Container, activeEvent.PrimarySource, activeEvent); break; case AudioContainerType.ContinuousRandom: PlayContinuousRandomContainer(activeEvent.AudioEvent.Container, activeEvent.PrimarySource, activeEvent); break; default: Debug.LogErrorFormat(this, "Trying to play container \"{0}\" with an unknown AudioContainerType \"{1}\".", activeEvent.AudioEvent.Container, activeEvent.AudioEvent.Container.ContainerType); // Clean up the ActiveEvent before we discard it, so it will release its AudioSource(s). activeEvent.Dispose(); break; } }