Game.Audio.AudioLoop
Assembly: Assembly-CSharp
Namespace: Game.Audio
Type: class
Base: System.Object
Summary:
Utility class that manages playback of a menu/ambient audio asset with optional looping. It loads AudioClip(s) from an AudioAsset, creates one or two Unity AudioSource components to support seamless loop handoff, controls volume and fade-out behavior, schedules playback using AudioSettings.dspTime, and cleans up created resources when finished. Designed for use in the game's audio/menu systems.
Fields
-
private const string kMenuCutoffProperty = "MenuCutoff"
Holds the AudioMixer exposed parameter name used to control a low-pass/high-cut filter (called "MenuCutoff") on the mixer while fading. -
private AudioAsset m_Asset
Reference to the AudioAsset (Colossal.IO.AssetDatabase) that provides the AudioClip(s) and loop metadata (loop start/end, durations, fadeout time, alternative start, etc). -
private AudioSource[] m_AudioSource
Array of one or two Unity AudioSource components created on a GameObject ("MenuAudioSource"). If the asset does not contain a separate loop region, only one AudioSource is used; if it does, two AudioSources are used to schedule seamless loop transitions. -
private int m_ActiveAudioSource
Index (0 or 1) into m_AudioSource indicating which AudioSource is currently active/controlling playback. -
private double m_NextCheck = -1.0
DSP time (AudioSettings.dspTime) for the next scheduled loop transition. -1 indicates no scheduled loop. -
private AudioMixerGroup m_group
AudioMixerGroup assigned to created AudioSource(s) (outputAudioMixerGroup). -
private AudioMixer m_Mixer
AudioMixer used to control the "MenuCutoff" parameter during fades. -
private float m_FadeOutTime
Remaining time (seconds) for an ongoing fade-out. When >0, Update interpolates mixer cutoff and source volume; when <0 triggers Dispose.
Properties
-
public float volume { get; private set }
Gets or sets the volume applied to the primary AudioSource and, if present, the secondary AudioSource. Setting writes to m_AudioSource[0].volume and m_AudioSource[1].volume when available. -
public bool isPlaying { get; private set }
Returns true if the currently active AudioSource exists and reports isPlaying; otherwise false. -
public double elapsedTime { get }
Read-only convenience property returning elapsed playback time (seconds) of the active AudioSource, computed from timeSamples and the clip frequency.
Constructors
public AudioLoop(AudioAsset asset, AudioMixer mixer, AudioMixerGroup group)
Creates a new AudioLoop for the given AudioAsset and assigns the provided AudioMixer and AudioMixerGroup. The constructor stores the references; the audio clip(s) and AudioSource GameObject(s) are created when Start(...) is invoked.
Methods
public async Task Start(bool useAlternativeStart = false)
Asynchronously loads the AudioClip(s) from the AudioAsset, creates a GameObject named "MenuAudioSource" and one or two AudioSource components, configures them (output group, dopplerLevel, spatialBlend, loop flags, clip, initial timeSamples if using an alternative start), sets initial volume, schedules the primary AudioSource to PlayScheduled at AudioSettings.dspTime, and if the asset contains a loop region prepares the secondary source and sets m_NextCheck for the first loop transition.
Notes: - The method sets the AudioMixer "MenuCutoff" parameter to 22000f (full bandwidth) at start. - If m_Asset.hasLoop is true, the asset's clip is loaded twice (once for the intro/start, once for the loop segment) and two AudioSources are created to allow scheduled handoff. - useAlternativeStart will offset the initial timeSamples by m_Asset.alternativeStart if available.
public void Update(double deltaTime)
Called every frame (or regularly) with deltaTime (seconds). Responsibilities:- If a looping asset is in use and AudioSettings.dspTime approaches m_NextCheck (within 5 seconds), schedule the handoff: SetScheduledEndTime on the outgoing source, set timeSamples on the incoming source to loopStart, PlayScheduled at m_NextCheck, swap m_ActiveAudioSource, and advance m_NextCheck by loopDuration.
- If a fade-out is active (m_FadeOutTime > 0), decrement the remaining fade time, update the mixer's "MenuCutoff" parameter via math.lerp and math.pow for a non-linear curve, and update the source volume proportionally to remaining fade time / asset.fadeoutTime.
- If m_FadeOutTime < 0, call Dispose() to clean up.
Implementation detail: Uses Unity.Mathematics.math.lerp, math.saturate, and math.pow for the cutoff interpolation and AudioSettings.dspTime for precise scheduling.
-
public void FadeOut()
Starts the fade-out sequence by setting m_FadeOutTime to m_Asset.fadeoutTime. Update will then perform mixer and volume interpolation until fade completes and disposal occurs. -
public void Stop()
Stops playback on any created AudioSource(s) immediately. Does not destroy clips or the GameObject; Dispose should be called to free resources. -
public void Dispose()
Stops playback, resets scheduling state, destroys loaded AudioClip(s) (calls Object.Destroy on clips) and destroys the AudioSource GameObject. Clears m_AudioSource to null to release references.
Usage Example
// Example usage in a MonoBehaviour or audio manager:
private AudioLoop menuLoop;
public async void PlayMenuMusic(AudioAsset asset, AudioMixer mixer, AudioMixerGroup group)
{
// Create the loop manager
menuLoop = new AudioLoop(asset, mixer, group);
// Start playback (await the async load and scheduling)
await menuLoop.Start(useAlternativeStart: false);
}
// In your update loop (e.g. MonoBehaviour.Update), forward delta time:
void Update()
{
if (menuLoop != null)
{
menuLoop.Update(Time.deltaTime);
}
}
// To fade out and clean up:
public void StopMenuMusic()
{
if (menuLoop != null)
{
menuLoop.FadeOut(); // progressive fade and cleanup
// or menuLoop.Stop(); menuLoop.Dispose(); for immediate stop+cleanup
}
}
Additional notes and caveats: - Start(...) is asynchronous; callers should await it or handle the returned Task to ensure the clip(s) are loaded before relying on playback. - For assets with hasLoop == true the class loads the clip twice (intro and loop clip). The second load allows seamless scheduling but increases memory usage; Dispose will destroy both clips. - The created GameObject is named "MenuAudioSource" and is not parented — be sure to call Dispose to remove it and avoid leaked GameObjects/audio clips. - The class manipulates an AudioMixer parameter named "MenuCutoff"; ensure your mixer exposes that parameter (or change the constant) to avoid missing-parameter errors.