277 lines
12 KiB
C#
277 lines
12 KiB
C#
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="AudioSourceExt.cs" company="VRMADA">
|
|
// Copyright (c) VRMADA, All rights reserved.
|
|
// </copyright>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using UltimateXR.Extensions.System;
|
|
using UltimateXR.Extensions.System.Threading;
|
|
using UnityEngine;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace UltimateXR.Extensions.Unity.Audio
|
|
{
|
|
/// <summary>
|
|
/// <see cref="AudioSource" /> extensions.
|
|
/// </summary>
|
|
public static class AudioSourceExt
|
|
{
|
|
#region Public Types & Data
|
|
|
|
/// <summary>
|
|
/// Default spatial blend for 3D positioned audio.
|
|
/// </summary>
|
|
public const float SpatialBlend3D = 0.9f;
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Ubiquitously plays an <see cref="AudioClip" />.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This function creates an <see cref="AudioSource" /> but automatically disposes of it once the clip has finished
|
|
/// playing.
|
|
/// </remarks>
|
|
/// <param name="clip">Reference to the sound clip file that will be played.</param>
|
|
/// <param name="volume">How loud the sound is at a distance of one world unit (one meter) [0.0, 1.0].</param>
|
|
/// <param name="delay">Delay time specified in seconds.</param>
|
|
/// <param name="pitch">
|
|
/// Amount of change in pitch due to slowdown/speed up of the Audio Clip. Value 1 is normal playback
|
|
/// speed.
|
|
/// </param>
|
|
/// <param name="offsetSeconds">Start offset in seconds</param>
|
|
/// <returns>The just created temporal <see cref="AudioSource" />.</returns>
|
|
public static AudioSource PlayClip(AudioClip clip,
|
|
float volume = 1.0f,
|
|
float delay = 0.0f,
|
|
float pitch = 1.0f,
|
|
float offsetSeconds = 0.0f)
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
throw new InvalidOperationException("Playback is only allowed while playing.");
|
|
}
|
|
clip.ThrowIfNull(nameof(clip));
|
|
volume = Mathf.Clamp01(volume);
|
|
pitch = Mathf.Clamp01(pitch);
|
|
|
|
var gameObject = new GameObject($"{nameof(AudioSourceExt)}_{nameof(PlayClip)}_{clip.name}");
|
|
var audioSource = gameObject.AddComponent<AudioSource>();
|
|
audioSource.clip = clip;
|
|
audioSource.volume = volume;
|
|
audioSource.pitch = pitch;
|
|
audioSource.spatialBlend = SpatialBlendUbiquitous;
|
|
|
|
if (offsetSeconds - delay >= clip.length)
|
|
{
|
|
audioSource.Stop();
|
|
Object.Destroy(gameObject, 1.0f);
|
|
return audioSource;
|
|
}
|
|
|
|
if (delay > offsetSeconds)
|
|
{
|
|
audioSource.PlayDelayed(delay - offsetSeconds);
|
|
}
|
|
else
|
|
{
|
|
audioSource.Play();
|
|
audioSource.time = offsetSeconds - delay;
|
|
}
|
|
|
|
float duration = (delay + clip.length - offsetSeconds) * (Time.timeScale < 0.00999999977648258 ? 0.01f : Time.timeScale);
|
|
Object.Destroy(gameObject, duration);
|
|
return audioSource;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Plays an AudioClip at a given position in world space.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This function creates an <see cref="AudioSource" /> but automatically disposes of it once the clip has finished
|
|
/// playing.
|
|
/// </remarks>
|
|
/// <param name="clip">Reference to the sound clip file that will be played.</param>
|
|
/// <param name="point">Position in world space from which sound originates.</param>
|
|
/// <param name="volume">How loud the sound is at a distance of one world unit (one meter) [0.0, 1.0].</param>
|
|
/// <param name="delay">Delay time specified in seconds.</param>
|
|
/// <param name="pitch">
|
|
/// Amount of change in pitch due to slowdown/speed up of the Audio Clip. Value 1 is normal playback
|
|
/// speed.
|
|
/// </param>
|
|
/// <param name="spatialBlend">Sets how much the 3D engine has an effect on the audio source [0.0, 1.0].</param>
|
|
/// <param name="offsetSeconds">Start offset in seconds</param>
|
|
/// <returns>The just created temporal <see cref="AudioSource" />.</returns>
|
|
/// <seealso cref="AudioSource.PlayClipAtPoint(AudioClip, Vector3, float)" />
|
|
public static AudioSource PlayClipAtPoint(AudioClip clip,
|
|
Vector3 point,
|
|
float volume = 1.0f,
|
|
float delay = 0.0f,
|
|
float pitch = 1.0f,
|
|
float spatialBlend = SpatialBlend3D,
|
|
float offsetSeconds = 0.0f)
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
throw new InvalidOperationException("Playback is only allowed while playing.");
|
|
}
|
|
|
|
clip.ThrowIfNull(nameof(clip));
|
|
volume = Mathf.Clamp01(volume);
|
|
spatialBlend = Mathf.Clamp01(spatialBlend);
|
|
|
|
var gameObject = new GameObject($"{nameof(AudioSourceExt)}_{nameof(PlayClipAtPoint)}_{clip.name}") { transform = { position = point } };
|
|
var audioSource = gameObject.AddComponent<AudioSource>();
|
|
audioSource.clip = clip;
|
|
audioSource.volume = volume;
|
|
audioSource.pitch = pitch;
|
|
audioSource.spatialBlend = spatialBlend;
|
|
|
|
if (offsetSeconds - delay >= clip.length)
|
|
{
|
|
audioSource.Stop();
|
|
Object.Destroy(gameObject, 1.0f);
|
|
return audioSource;
|
|
}
|
|
|
|
audioSource.Play();
|
|
offsetSeconds = Mathf.Max(offsetSeconds, 0.0f);
|
|
|
|
if (delay > offsetSeconds)
|
|
{
|
|
audioSource.PlayDelayed(delay - offsetSeconds);
|
|
}
|
|
else
|
|
{
|
|
audioSource.Play();
|
|
audioSource.time = offsetSeconds - delay;
|
|
}
|
|
|
|
float duration = (delay + clip.length - offsetSeconds) * (Time.timeScale < 0.00999999977648258 ? 0.01f : Time.timeScale);
|
|
Object.Destroy(gameObject, duration);
|
|
return audioSource;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Asynchronous and ubiquitously plays an <see cref="AudioClip" />.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This function creates an <see cref="AudioSource" /> but automatically disposes of it once the clip has finished
|
|
/// playing.
|
|
/// </remarks>
|
|
/// <param name="clip">Reference to the sound clip file that will be played.</param>
|
|
/// <param name="volume">How loud the sound is at a distance of one world unit (one meter) [0.0, 1.0].</param>
|
|
/// <param name="delay">Delay time specified in seconds.</param>
|
|
/// <param name="pitch">
|
|
/// Amount of change in pitch due to slowdown/speed up of the Audio Clip. Value 1 is normal playback
|
|
/// speed.
|
|
/// </param>
|
|
/// <param name="offsetSeconds">Start offset in seconds</param>
|
|
/// <param name="ct"><see cref="CancellationToken" /> to stop playing.</param>
|
|
/// <returns>An awaitable <see cref="Task" />.</returns>
|
|
public static async Task PlayClipAsync(AudioClip clip,
|
|
float volume = 1.0f,
|
|
float delay = 0.0f,
|
|
float pitch = 1.0f,
|
|
float offsetSeconds = 0.0f,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (ct.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
if (!Application.isPlaying)
|
|
{
|
|
throw new InvalidOperationException("Playback is only allowed while playing.");
|
|
}
|
|
if (offsetSeconds >= clip.length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
offsetSeconds = Mathf.Max(offsetSeconds, 0.0f);
|
|
|
|
float duration = (delay + clip.length - offsetSeconds) * (Time.timeScale < 0.00999999977648258 ? 0.01f : Time.timeScale);
|
|
AudioSource audioSource = PlayClip(clip, volume, delay, pitch, offsetSeconds);
|
|
await TaskExt.Delay(duration, ct);
|
|
|
|
if (ct.IsCancellationRequested && audioSource != null)
|
|
{
|
|
audioSource.Stop();
|
|
Object.Destroy(audioSource.gameObject);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously plays an <see cref="AudioClip" /> at a given position in world space.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This function creates an <see cref="AudioSource" /> but automatically disposes of it once the clip has finished
|
|
/// playing.
|
|
/// </remarks>
|
|
/// <param name="clip">Reference to the sound clip file that will be played.</param>
|
|
/// <param name="point">Position in world space from which sound originates.</param>
|
|
/// <param name="volume">How loud the sound is at a distance of one world unit (one meter) [0.0, 1.0].</param>
|
|
/// <param name="delay">Delay time specified in seconds.</param>
|
|
/// <param name="pitch">
|
|
/// Amount of change in pitch due to slowdown/speed up of the Audio Clip. Value 1 is normal playback
|
|
/// speed.
|
|
/// </param>
|
|
/// <param name="spatialBlend">Sets how much the 3D engine has an effect on the audio source [0.0, 1.0].</param>
|
|
/// <param name="offsetSeconds">Start offset in seconds</param>
|
|
/// <param name="ct"><see cref="CancellationToken" /> to stop playing.</param>
|
|
/// <returns>An awaitable <see cref="Task" />.</returns>
|
|
public static async Task PlayClipAtPointAsync(AudioClip clip,
|
|
Vector3 point,
|
|
float volume = 1.0f,
|
|
float delay = 0.0f,
|
|
float pitch = 1.0f,
|
|
float spatialBlend = SpatialBlend3D,
|
|
float offsetSeconds = 0.0f,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (ct.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
if (!Application.isPlaying)
|
|
{
|
|
throw new InvalidOperationException("Playback is only allowed while playing.");
|
|
}
|
|
|
|
if (offsetSeconds >= clip.length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
offsetSeconds = Mathf.Max(offsetSeconds, 0.0f);
|
|
|
|
float duration = (delay + clip.length - offsetSeconds) * (Time.timeScale < 0.00999999977648258 ? 0.01f : Time.timeScale);
|
|
AudioSource audioSource = PlayClipAtPoint(clip, point, volume, delay, pitch, spatialBlend, offsetSeconds);
|
|
|
|
await TaskExt.Delay(duration, ct);
|
|
|
|
if (ct.IsCancellationRequested && audioSource != null)
|
|
{
|
|
audioSource.Stop();
|
|
Object.Destroy(audioSource.gameObject);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Types & Data
|
|
|
|
/// <summary>
|
|
/// Spatial blend for ubiquitous playback.
|
|
/// </summary>
|
|
private const float SpatialBlendUbiquitous = 0f;
|
|
|
|
#endregion
|
|
}
|
|
} |