Files
GoakeFlax/Source/Game/Audio/AudioManager.cs

256 lines
9.6 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using FlaxEngine;
using Console = Game.Console;
using Object = FlaxEngine.Object;
namespace Game
{
[Flags]
public enum AudioFlags
{
None = 0,
/// Avoid replacing the existing playing audio source in this channel.
ContinuePlayingExistingSource = 1
}
public static class AudioManager
{
private static readonly Random random = new Random();
private static readonly Dictionary<string, AudioInfo> cachedAudioInfos = new Dictionary<string, AudioInfo>();
private static readonly Dictionary<Actor, ActorAudioChannels> actorAudioChannels =
new Dictionary<Actor, ActorAudioChannels>();
public static void PlaySound(string soundName, Actor actor, Float3 position, float volume = 1f)
{
PlaySoundInternal(soundName, actor, 0, AudioFlags.None, position, volume, Float2.One, Float2.Zero);
}
public static void PlaySound(string soundName, Actor actor, Float3 position, float volume, Float2 pitchRange)
{
PlaySoundInternal(soundName, actor, 0, AudioFlags.None, position, volume, pitchRange, Float2.Zero);
}
public static void PlaySound(string soundName, Actor actor, int channel, Float3 position, float volume = 1f)
{
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, Float2.One, Float2.Zero);
}
public static void PlaySound(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
float volume = 1f)
{
PlaySoundInternal(soundName, actor, channel, flags, position, volume, Float2.One, Float2.Zero);
}
public static void PlaySound(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
float volume, Float2 pitchRange)
{
PlaySoundInternal(soundName, actor, channel, flags, position, volume, pitchRange, Float2.Zero);
}
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
Float3 position, float volume = 1f)
{
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, Float2.One, delayRange);
}
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
Float3 position, float volume, Float2 pitchRange)
{
PlaySoundInternal(soundName, actor, channel, AudioFlags.None, position, volume, pitchRange, delayRange);
}
public static void PlaySoundDelayed(Float2 delayRange, string soundName, Actor actor, int channel,
AudioFlags flags, Float3 position, float volume, Float2 pitchRange)
{
PlaySoundInternal(soundName, actor, channel, flags, position, volume, pitchRange, delayRange);
}
private static void PlaySoundInternal(string soundName, Actor actor, int channel, AudioFlags flags, Float3 position,
float volume, Float2 pitchRange, Float2 delayRange)
{
AudioInfo audio = GetSound(soundName);
if (audio.AudioClips.Length == 0)
return;
channel = Math.Max(channel, 0);
ActorAudioChannels actorChannels = null;
if (channel > 0)
{
if (!actorAudioChannels.TryGetValue(actor, out actorChannels))
{
actorChannels = new ActorAudioChannels();
actorAudioChannels.Add(actor, actorChannels);
}
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
actorChannels.channelSources.Add(channel, null);
if (existingAudioSource && existingAudioSource != null &&
existingAudioSource.State == AudioSource.States.Playing)
{
if (flags.HasFlag(AudioFlags.ContinuePlayingExistingSource))
return;
existingAudioSource.Stop();
Object.Destroy(existingAudioSource);
actorChannels.channelSources[channel] = null;
}
}
AudioClip audioClip;
if (audio.AudioClips.Length > 1)
{
// Randomize selected clip while avoiding the last used clip
int randomIndex = 0;
for (int i = 0; i < 10; i++)
{
randomIndex = random.Next(audio.AudioClips.Length);
if (randomIndex != audio.lastAudioPlayed)
break;
}
audioClip = audio.AudioClips[randomIndex];
audio.lastAudioPlayed = randomIndex;
}
else
{
audioClip = audio.AudioClips[0];
}
// Randomized pitch
float pitch;
if (pitchRange[0] < pitchRange[1])
pitch = (float)(pitchRange[0] + random.NextDouble() * (pitchRange[1] - pitchRange[0]));
else
pitch = pitchRange[0];
// Randomized start delay
float randomDelay;
if (delayRange[0] < delayRange[1])
randomDelay = (float)(delayRange[0] + random.NextDouble() * (delayRange[1] - delayRange[0]));
else
randomDelay = delayRange[0];
AudioSourceDelayed audioSource = new AudioSourceDelayed();
audioSource.Delay = randomDelay;
audioSource.Clip = audioClip;
audioSource.Position = position;
audioSource.Parent = actor.Parent;
audioSource.Pitch = pitch;
audioSource.Name = Path.GetFileNameWithoutExtension(audioClip.Path);
audioSource.Volume = volume;
audioSource.PlayOnStart = randomDelay == 0;
if (volume != 1f)
audioSource.Name += ", vol: " + volume;
if (pitch != 1f)
audioSource.Name += ", pitch: " + pitch;
if (pitch != 0f)
audioSource.Name += ", delay: " + randomDelay;
Console.Print("playing sound " + audioSource.Name);
if (channel > 0)
actorChannels.channelSources[channel] = audioSource;
}
public static void StopSound(Actor actor, int channel)
{
if (channel <= 0)
return;
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
return;
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
return;
if (existingAudioSource && existingAudioSource != null &&
existingAudioSource.State == AudioSource.States.Playing)
{
existingAudioSource.Stop();
Object.Destroy(existingAudioSource);
actorChannels.channelSources[channel] = null;
}
}
public static bool IsSoundPlaying(Actor actor, int channel)
{
if (channel <= 0)
return false;
if (!actorAudioChannels.TryGetValue(actor, out ActorAudioChannels actorChannels))
return false;
if (!actorChannels.channelSources.TryGetValue(channel, out AudioSource existingAudioSource))
return false;
if (existingAudioSource && existingAudioSource != null &&
existingAudioSource.State == AudioSource.States.Playing)
return true;
return false;
}
private static AudioInfo GetSound(string soundName)
{
if (cachedAudioInfos.TryGetValue(soundName, out AudioInfo cachedAudio))
return cachedAudio;
AudioInfo audio = new AudioInfo();
string audioBasePath = Path.Combine(AssetManager.ContentPath, "Audio");
AudioClip audioClip = Content.Load<AudioClip>(Path.Combine(audioBasePath, soundName + ".flax"));
if (audioClip != null)
{
audio.AudioClips = new[] { audioClip };
}
else
{
// Check if this audio has multiple variations
var audioClips = new List<AudioClip>();
for (int i = 1; i < 50; i++)
{
// TODO: make this more efficient, maybe get a list of assets and filter by name?
AudioClip audioClipVariation =
Content.Load<AudioClip>(Path.Combine(audioBasePath, soundName + "_var" + i + ".flax"));
if (audioClipVariation == null)
break;
audioClips.Add(audioClipVariation);
}
if (audioClips.Count > 0)
audio.AudioClips = audioClips.ToArray();
else
Console.PrintError("AudioClip '" + soundName + "' not found");
}
audio.lastAudioPlayed = audio.AudioClips.Length + 1;
cachedAudioInfos.Add(soundName, audio);
return audio;
}
private class AudioInfo
{
public AudioClip[] AudioClips;
public int lastAudioPlayed;
}
private class ActorAudioChannels
{
public readonly Dictionary<int, AudioSource> channelSources;
public ActorAudioChannels()
{
channelSources = new Dictionary<int, AudioSource>();
}
}
}
}