public static AudioClip PopulateResourceSongToPlaylistController(string controllerName, string songResourceName,
                                                                         string playlistName)
        {
            var resAudioClip = Resources.Load(songResourceName) as AudioClip;

            if (resAudioClip == null)
            {
                MasterAudio.LogWarning("Resource file '" + songResourceName + "' could not be located from Playlist '" +
                                       playlistName + "'.");
                return(null);
            }

            if (!AudioUtil.AudioClipWillPreload(resAudioClip))
            {
                MasterAudio.LogWarning("Audio Clip for Resource file '" + songResourceName + "' from Playlist '" +
                                       playlistName + "' has 'Preload Audio Data' turned off, which can cause audio glitches. Resource files should always Preload Audio Data. Please turn it on.");
            }

            FinishRecordingPlaylistClip(controllerName, resAudioClip);

            return(resAudioClip);
        }
        /// <summary>
        /// Populates the sources with resource clip.
        /// </summary>
        /// <returns><c>true</c>, if sources with resource clip was populated, <c>false</c> otherwise.</returns>
        /// <param name="clipName">Clip name.</param>
        /// <param name="variation">Variation.</param>
        public static bool PopulateSourcesWithResourceClip(string clipName, SoundGroupVariation variation)
        {
            if (AudioClipsByName.ContainsKey(clipName))
            {
                //MasterAudio.Log("clip already exists: " + clipName);
                return(true); // work is done already!
            }

            var resAudioClip = Resources.Load(clipName) as AudioClip;

            if (resAudioClip == null)
            {
                MasterAudio.LogError("Resource file '" + clipName + "' could not be located.");
                return(false);
            }

            if (!AudioResourceTargetsByName.ContainsKey(clipName))
            {
                MasterAudio.LogError("No Audio Sources found to add Resource file '" + clipName + "'.");
                return(false);
            }

            var sources = AudioResourceTargetsByName[clipName];

            // ReSharper disable once ForCanBeConvertedToForeach
            for (var i = 0; i < sources.Count; i++)
            {
                sources[i].clip = resAudioClip;
            }

            if (!AudioUtil.AudioClipWillPreload(resAudioClip))
            {
                MasterAudio.LogWarning("Audio Clip for Resource file '" + clipName + "' of Sound Group '" + variation.ParentGroup.name + "' has 'Preload Audio Data' turned off, which can cause audio glitches. Resource files should always Preload Audio Data. Please turn it on.");
            }

            AudioClipsByName.Add(clipName, resAudioClip);
            return(true);
        }
        /// <summary>
        /// Populates the sources with resource clip, non-thread blocking.
        /// </summary>
        /// <param name="clipName">Clip name.</param>
        /// <param name="variation">Variation.</param>
        /// <param name="successAction">Method to execute if successful.</param>
        /// <param name="failureAction">Method to execute if not successful.</param>
        public static IEnumerator PopulateSourcesWithResourceClipAsync(string clipName, SoundGroupVariation variation,
                                                                       // ReSharper disable RedundantNameQualifier
                                                                       System.Action successAction, System.Action failureAction)
        {
            // ReSharper restore RedundantNameQualifier
            if (AudioClipsByName.ContainsKey(clipName))
            {
                if (successAction != null)
                {
                    successAction();
                }

                yield break;
            }

            var asyncRes = Resources.LoadAsync(clipName, typeof(AudioClip));

            while (!asyncRes.isDone)
            {
                yield return(MasterAudio.EndOfFrameDelay);
            }

            var resAudioClip = asyncRes.asset as AudioClip;

            if (resAudioClip == null)
            {
                MasterAudio.LogError("Resource file '" + clipName + "' could not be located.");

                if (failureAction != null)
                {
                    failureAction();
                }
                yield break;
            }

            if (!AudioResourceTargetsByName.ContainsKey(clipName))
            {
                MasterAudio.LogError("No Audio Sources found to add Resource file '" + clipName + "'.");

                if (failureAction != null)
                {
                    failureAction();
                }
                yield break;
            }

            if (!AudioUtil.AudioClipWillPreload(resAudioClip))
            {
                MasterAudio.LogWarning("Audio Clip for Resource file '" + clipName + "' of Sound Group '" + variation.ParentGroup.name + "' has 'Preload Audio Data' turned off, which can cause audio glitches. Resource files should always Preload Audio Data. Please turn it on.");
            }

            var sources = AudioResourceTargetsByName[clipName];

            // ReSharper disable once ForCanBeConvertedToForeach
            for (var i = 0; i < sources.Count; i++)
            {
                sources[i].clip = resAudioClip;
            }

            if (!AudioClipsByName.ContainsKey(clipName))
            {
                AudioClipsByName.Add(clipName, resAudioClip);
            }

            if (successAction != null)
            {
                successAction();
            }
        }
        private static readonly object _syncRoot = new object(); // to lock below

        /// <summary>
        /// Start Coroutine when calling this, passing in success and failure action delegates.
        /// </summary>
        /// <param name="addressable"></param>
        /// <param name="variation"></param>
        /// <param name="successAction"></param>
        /// <param name="failureAction"></param>
        /// <returns></returns>
        public static IEnumerator PopulateSourceWithAddressableClipAsync(AssetReference addressable, SoundGroupVariation variation, int unusedSecondsLifespan,
                                                                         System.Action successAction,
                                                                         System.Action failureAction)
        {
            var isWarmingCall = MasterAudio.IsWarming; // since this may change by the time we load the asset, we store it so we can know.

            if (!IsAddressableValid(addressable))
            {
                if (failureAction != null)
                {
                    failureAction();
                }
                if (isWarmingCall)
                {
                    DTMonoHelper.SetActive(variation.GameObj, false); // should disable itself
                }
                yield break;
            }

            var addressableId = GetAddressableId(addressable);

            AsyncOperationHandle <AudioClip> loadHandle;
            AudioClip addressableClip;
            var       shouldReleaseLoadedAssetNow = false;

            if (AddressableTasksByAddressibleId.ContainsKey(addressableId))
            {
                loadHandle      = AddressableTasksByAddressibleId[addressableId].AssetHandle;
                addressableClip = loadHandle.Result;
            }
            else
            {
                loadHandle = addressable.LoadAssetAsync <AudioClip>();

                while (!loadHandle.IsDone)
                {
                    yield return(MasterAudio.EndOfFrameDelay);
                }

                addressableClip = loadHandle.Result;

                if (addressableClip == null || loadHandle.Status != AsyncOperationStatus.Succeeded)
                {
                    var errorText = "";
                    if (loadHandle.OperationException != null)
                    {
                        errorText = " Exception: " + loadHandle.OperationException.Message;
                    }
                    MasterAudio.LogError("Addressable file for '" + variation.name + "' could not be located." + errorText);

                    if (failureAction != null)
                    {
                        failureAction();
                    }
                    if (isWarmingCall)
                    {
                        DTMonoHelper.SetActive(variation.GameObj, false); // should disable itself
                    }
                    yield break;
                }

                lock (_syncRoot) {
                    if (!AddressableTasksByAddressibleId.ContainsKey(addressableId))
                    {
                        AddressableTasksByAddressibleId.Add(addressableId, new AddressableTracker <AudioClip>(loadHandle, unusedSecondsLifespan));
                    }
                    else
                    {
                        // race condition reached. Another load finished before this one. Throw this away and use the other, to release memory.
                        shouldReleaseLoadedAssetNow = true;
                        addressableClip             = AddressableTasksByAddressibleId[addressableId].AssetHandle.Result;
                    }
                }
            }

            if (shouldReleaseLoadedAssetNow)
            {
                Addressables.Release(loadHandle);
            }

            if (!AudioUtil.AudioClipWillPreload(addressableClip))
            {
                MasterAudio.LogWarning("Audio Clip for Addressable file '" + addressableClip.name + "' of Sound Group '" + variation.ParentGroup.name + "' has 'Preload Audio Data' turned off, which can cause audio glitches. Addressables should always Preload Audio Data. Please turn it on.");
            }

            variation.LoadStatus = MasterAudio.VariationLoadStatus.Loaded;

            var stoppedBeforePlay = variation.IsStopRequested;

            if (stoppedBeforePlay)
            {
                // do nothing, but don't call the delegate or set audio clip for sure!
            }
            else
            {
                variation.VarAudio.clip = addressableClip;
                if (successAction != null)
                {
                    successAction();
                }
            }
        }
        public static IEnumerator PopulateAddressableSongToPlaylistControllerAsync(MusicSetting setting, AssetReference addressable,
                                                                                   PlaylistController playlistController, PlaylistController.AudioPlayType playType)
        {
            if (!IsAddressableValid(addressable))
            {
                yield break;
            }

            var addressableId = GetAddressableId(addressable);

            AsyncOperationHandle <AudioClip> loadHandle;
            AudioClip addressableClip;
            var       shouldReleaseLoadedAssetNow = false;

            if (AddressableTasksByAddressibleId.ContainsKey(addressableId))
            {
                loadHandle      = AddressableTasksByAddressibleId[addressableId].AssetHandle;
                addressableClip = loadHandle.Result;
            }
            else
            {
                loadHandle = addressable.LoadAssetAsync <AudioClip>();

                while (!loadHandle.IsDone)
                {
                    yield return(MasterAudio.EndOfFrameDelay);
                }

                addressableClip = loadHandle.Result;

                if (addressableClip == null || loadHandle.Status != AsyncOperationStatus.Succeeded)
                {
                    var errorText = "";
                    if (loadHandle.OperationException != null)
                    {
                        errorText = " Exception: " + loadHandle.OperationException.Message;
                    }
                    MasterAudio.LogError("Addressable file for PlaylistController '" + playlistController.name + "' could not be located." + errorText);
                    yield break;
                }

                lock (_syncRoot) {
                    if (!AddressableTasksByAddressibleId.ContainsKey(addressableId))
                    {
                        AddressableTasksByAddressibleId.Add(addressableId, new AddressableTracker <AudioClip>(loadHandle, 0));
                    }
                    else
                    {
                        // race condition reached. Another load finished before this one. Throw this away and use the other, to release memory.
                        shouldReleaseLoadedAssetNow = true;
                        addressableClip             = AddressableTasksByAddressibleId[addressableId].AssetHandle.Result;
                    }
                }
            }

            if (shouldReleaseLoadedAssetNow)
            {
                Addressables.Release(loadHandle);
            }

            if (!AudioUtil.AudioClipWillPreload(addressableClip))
            {
                MasterAudio.LogWarning("Audio Clip for Addressable file '" + addressableClip.name + "' of Playlist Controller '" + playlistController.name + "' has 'Preload Audio Data' turned off, which can cause audio glitches. Addressables should always Preload Audio Data. Please turn it on.");
            }

            // Figure out how to detect stop before loaded, if needed
            var stoppedBeforePlay = false;

            if (stoppedBeforePlay)
            {
                // do nothing, but don't call the delegate or set audio clip for sure!
            }
            else
            {
                playlistController.FinishLoadingNewSong(setting, addressableClip, playType);
            }
        }
        private void UpdateOcclusion()
        {
            var hasOcclusionOn = GrpVariation.UsesOcclusion;

            if (!hasOcclusionOn)
            {
                if (!_occlusionOnLastFrame)
                {
                    return;
                }

                _occlusionOnLastFrame = false;
                MasterAudio.StopTrackingOcclusionForSource(GrpVariation.GameObj);
                ResetToNonOcclusionSetting();

                return;
            }

            _occlusionOnLastFrame = true;

            if (_listenerThisFrame == null)
            {
                // cannot occlude without something to raycast at.
                return;
            }

            if (Time.realtimeSinceStartup - _occlusionLastCalculated <= MasterAudio.ReOccludeCheckTime)
            {
                // too early, abort and try next frame
                return;
            }

            var lastCalcTime = Time.realtimeSinceStartup;

            if (_occlusionLastCalculated == FakeNegativeFloatValue)
            {
                // spread out the line casts so they're not all on the same frame (for ambient sounds that all start in the Scene).
                lastCalcTime += MasterAudio.ReOccludeCheckTime * Random.Range(0f, 0.9f);
            }

            _occlusionLastCalculated = lastCalcTime;

            var direction          = _listenerThisFrame.position - Trans.position;
            var distanceToListener = direction.magnitude;

            if (distanceToListener > VarAudio.maxDistance)
            {
                // out of hearing range, no reason to calculate occlusion.
                MasterAudio.AddToOcclusionOutOfRangeSources(GrpVariation.GameObj);
                ResetToNonOcclusionSetting();
                return;
            }

            MasterAudio.AddToOcclusionInRangeSources(GrpVariation.GameObj);

            if (GrpVariation.LowPassFilter == null)
            {
                // in case Occlusion got turned on during runtime.
                GrpVariation.gameObject.AddComponent <AudioLowPassFilter>();
            }

                        #if UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1
                        #else
            var is2DRaycast = _maThisFrame.occlusionRaycastMode == MasterAudio.RaycastMode.Physics2D;

            var oldQueriesStart = Physics2D.queriesStartInColliders;
            if (is2DRaycast)
            {
                Physics2D.queriesStartInColliders = _maThisFrame.occlusionIncludeStartRaycast2DCollider;
            }
                        #endif

            var   hitPoint    = Vector3.zero;
            float?hitDistance = null;
            var   isHit       = false;

            if (_maThisFrame.occlusionUseLayerMask)
            {
                switch (_maThisFrame.occlusionRaycastMode)
                {
                case MasterAudio.RaycastMode.Physics3D:
                    RaycastHit hitObject;
                    if (Physics.Raycast(Trans.position, direction, out hitObject, distanceToListener, _maThisFrame.occlusionLayerMask.value))
                    {
                        isHit       = true;
                        hitPoint    = hitObject.point;
                        hitDistance = hitObject.distance;
                    }
                    break;

                case MasterAudio.RaycastMode.Physics2D:
                    var castHit2D = Physics2D.Raycast(Trans.position, direction, distanceToListener, _maThisFrame.occlusionLayerMask.value);
                    if (castHit2D.transform != null)
                    {
                        isHit       = true;
                        hitPoint    = castHit2D.point;
                        hitDistance = castHit2D.distance;
                    }
                    break;
                }
            }
            else
            {
                switch (_maThisFrame.occlusionRaycastMode)
                {
                case MasterAudio.RaycastMode.Physics3D:
                    RaycastHit hitObject;
                    if (Physics.Raycast(Trans.position, direction, out hitObject, distanceToListener))
                    {
                        isHit       = true;
                        hitPoint    = hitObject.point;
                        hitDistance = hitObject.distance;
                    }
                    break;

                case MasterAudio.RaycastMode.Physics2D:
                    var castHit2D = Physics2D.Raycast(Trans.position, direction, distanceToListener);
                    if (castHit2D.transform != null)
                    {
                        isHit       = true;
                        hitPoint    = castHit2D.point;
                        hitDistance = castHit2D.distance;
                    }
                    break;
                }
            }

#if UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1
#else
            if (is2DRaycast)
            {
                Physics2D.queriesStartInColliders = oldQueriesStart;
            }
#endif

            var endPoint  = isHit ? hitPoint : _listenerThisFrame.position;
            var lineColor = isHit ? Color.red : Color.green;

            if (_maThisFrame.occlusionShowRaycasts)
            {
                Debug.DrawLine(Trans.position, endPoint, lineColor, .1f);
            }

            if (!isHit)
            {
                // ReSharper disable once PossibleNullReferenceException
                MasterAudio.RemoveFromBlockedOcclusionSources(GrpVariation.GameObj);
                ResetToNonOcclusionSetting();
                return;
            }

            MasterAudio.AddToBlockedOcclusionSources(GrpVariation.GameObj);

            var ratioToEdgeOfSound = hitDistance.Value / VarAudio.maxDistance;
            var filterFrequency    = AudioUtil.GetOcclusionCutoffFrequencyByDistanceRatio(ratioToEdgeOfSound);

            // ReSharper disable once PossibleNullReferenceException
            GrpVariation.LowPassFilter.cutoffFrequency = filterFrequency;
        }
        /// <summary>
        /// This method is called in a batch from ListenerFollower
        /// </summary>
        /// <returns></returns>
        public bool RayCastForOcclusion()
        {
            DoneWithOcclusion();

            var raycastOrigin = Trans.position;

            var offset = RayCastOriginOffset;

            if (offset > 0)
            {
                raycastOrigin = Vector3.MoveTowards(raycastOrigin, _listenerThisFrame.position, offset);
            }

            var direction          = _listenerThisFrame.position - raycastOrigin;
            var distanceToListener = direction.magnitude;

            if (distanceToListener > VarAudio.maxDistance)
            {
                // out of hearing range, no reason to calculate occlusion.
                MasterAudio.AddToOcclusionOutOfRangeSources(GrpVariation.GameObj);
                ResetToNonOcclusionSetting();
                return(false);
            }

            MasterAudio.AddToOcclusionInRangeSources(GrpVariation.GameObj);
            var is2DRaycast = _maThisFrame.occlusionRaycastMode == MasterAudio.RaycastMode.Physics2D;

            if (GrpVariation.LowPassFilter == null)
            {
                // in case Occlusion got turned on during runtime.
                var newFilter = GrpVariation.gameObject.AddComponent <AudioLowPassFilter>();
                GrpVariation.LowPassFilter = newFilter;
            }

            var oldQueriesStart = Physics2D.queriesStartInColliders;

            if (is2DRaycast)
            {
                Physics2D.queriesStartInColliders = _maThisFrame.occlusionIncludeStartRaycast2DCollider;
            }

            var oldRaycastsHitTriggers = true;

            // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
            if (is2DRaycast)
            {
                oldRaycastsHitTriggers       = Physics2D.queriesHitTriggers;
                Physics2D.queriesHitTriggers = _maThisFrame.occlusionRaycastsHitTriggers;
            }
            else
            {
                oldRaycastsHitTriggers     = Physics.queriesHitTriggers;
                Physics.queriesHitTriggers = _maThisFrame.occlusionRaycastsHitTriggers;
            }

            var   hitPoint    = Vector3.zero;
            float?hitDistance = null;
            var   isHit       = false;

            if (_maThisFrame.occlusionUseLayerMask)
            {
                switch (_maThisFrame.occlusionRaycastMode)
                {
                case MasterAudio.RaycastMode.Physics3D:
                    RaycastHit hitObject;
                    if (Physics.Raycast(raycastOrigin, direction, out hitObject, distanceToListener, _maThisFrame.occlusionLayerMask.value))
                    {
                        isHit       = true;
                        hitPoint    = hitObject.point;
                        hitDistance = hitObject.distance;
                    }

                    break;

                case MasterAudio.RaycastMode.Physics2D:
                    var castHit2D = Physics2D.Raycast(raycastOrigin, direction, distanceToListener, _maThisFrame.occlusionLayerMask.value);
                    if (castHit2D.transform != null)
                    {
                        isHit       = true;
                        hitPoint    = castHit2D.point;
                        hitDistance = castHit2D.distance;
                    }

                    break;
                }
            }
            else
            {
                switch (_maThisFrame.occlusionRaycastMode)
                {
                case MasterAudio.RaycastMode.Physics3D:
                    RaycastHit hitObject;
                    if (Physics.Raycast(raycastOrigin, direction, out hitObject, distanceToListener))
                    {
                        isHit       = true;
                        hitPoint    = hitObject.point;
                        hitDistance = hitObject.distance;
                    }

                    break;

                case MasterAudio.RaycastMode.Physics2D:
                    var castHit2D = Physics2D.Raycast(raycastOrigin, direction, distanceToListener);
                    if (castHit2D.transform != null)
                    {
                        isHit       = true;
                        hitPoint    = castHit2D.point;
                        hitDistance = castHit2D.distance;
                    }

                    break;
                }
            }

            if (is2DRaycast)
            {
                Physics2D.queriesStartInColliders = oldQueriesStart;
                Physics2D.queriesHitTriggers      = oldRaycastsHitTriggers;
            }
            else
            {
                Physics.queriesHitTriggers = oldRaycastsHitTriggers;
            }

            if (_maThisFrame.occlusionShowRaycasts)
            {
                var endPoint  = isHit ? hitPoint : _listenerThisFrame.position;
                var lineColor = isHit ? Color.red : Color.green;
                Debug.DrawLine(raycastOrigin, endPoint, lineColor, .1f);
            }

            if (!isHit)
            {
                // ReSharper disable once PossibleNullReferenceException
                MasterAudio.RemoveFromBlockedOcclusionSources(GrpVariation.GameObj);
                ResetToNonOcclusionSetting();
                return(true);
            }

            MasterAudio.AddToBlockedOcclusionSources(GrpVariation.GameObj);

            var ratioToEdgeOfSound = hitDistance.Value / VarAudio.maxDistance;
            var filterFrequency    = AudioUtil.GetOcclusionCutoffFrequencyByDistanceRatio(ratioToEdgeOfSound, this);

            var fadeTime = _maThisFrame.occlusionFreqChangeSeconds;

            if (fadeTime <= MasterAudio.InnerLoopCheckInterval)   // fast, just do it instantly.
            // ReSharper disable once PossibleNullReferenceException
            {
                GrpVariation.LowPassFilter.cutoffFrequency = filterFrequency;
                return(true);
            }

            MasterAudio.GradualOcclusionFreqChange(GrpVariation, fadeTime, filterFrequency);

            return(true);
        }
Exemple #8
0
        private void PlaySoundAndWait()
        {
            if (VarAudio.clip == null)   // in case the warming sound is an "internet file"
            {
                return;
            }

            double startTime = AudioSettings.dspTime;

            if (GrpVariation.PlaySoundParm.TimeToSchedulePlay.HasValue)
            {
                startTime = GrpVariation.PlaySoundParm.TimeToSchedulePlay.Value;
            }

            var delayTime = 0f;

            if (GrpVariation.useIntroSilence && GrpVariation.introSilenceMax > 0f)
            {
                var rndSilence = Random.Range(GrpVariation.introSilenceMin, GrpVariation.introSilenceMax);
                delayTime += rndSilence;
            }

            delayTime += GrpVariation.PlaySoundParm.DelaySoundTime;

            if (delayTime > 0f)
            {
                startTime += delayTime;
            }

            VarAudio.PlayScheduled(startTime);

            switch (GrpVariation.audLocation)
            {
#if ADDRESSABLES_ENABLED
            case MasterAudio.AudioLocation.Addressable:
                AudioAddressableOptimizer.AddAddressablePlayingClip(GrpVariation.audioClipAddressable, VarAudio);
                break;
#endif
            default:
                AudioUtil.ClipPlayed(VarAudio.clip, GrpVariation.GameObj);
                break;
            }

            if (GrpVariation.useRandomStartTime)
            {
                VarAudio.time = ClipStartPosition;

                if (!VarAudio.loop)   // don't stop it if it's going to loop.
                {
                    var playableLength = AudioUtil.AdjustAudioClipDurationForPitch(ClipEndPosition - ClipStartPosition, VarAudio);
                    _clipSchedEndTime = startTime + playableLength;
                    VarAudio.SetScheduledEndTime(_clipSchedEndTime.Value);
                }
            }

            GrpVariation.LastTimePlayed = AudioUtil.Time;

            DuckIfNotSilent();

            _isPlayingBackward = GrpVariation.OriginalPitch < 0;
            _lastFrameClipTime = _isPlayingBackward ? ClipEndPosition + 1 : -1f;

            _waitMode = WaitForSoundFinishMode.WaitForEnd;
        }