using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Audio; using UnityEngine.AddressableAssets; using UnityEngine.Assertions; using asap.core.common; using UnityEngine.ResourceManagement.AsyncOperations; using System.Threading; using System; using UniRx; namespace asap.core { public interface ISoundService { [Inject] public IObjectPoolService poolService { set; } public int MasterVol { get; set; } public int BGMVol { get; set; } public int SFXVol { get; set; } public int VoiceVol { get; set; } public Task InitAsync(string mixerAddr = "AudioMixer", string soundPrefabAddr = "sound", string bgmGroupId = "BGM", string fishingGroupId = "Fishing", string uiGroupId = "UI", string voiceGroupId = "Voice"); public void Release(); public Sound GetNewBgmSound(AudioClip clip, bool loop = true, int priority = 0, float time = 0); public Sound GetNewFishingSound(AudioClip clip, bool loop = false, int priority = 128, float time = 0); public Sound GetNewUISound(AudioClip clip, bool loop = false, int priority = 128, float time = 0); public Sound GetNewVoiceSound(AudioClip clip); public void ReturnSound(Sound sound); public void FadeIn(Sound sound, float duration); public void FadeOut(Sound sound, float duration); } public struct LowpassEffectEvt { public bool active; public float transTime; } public class SoundService : ISoundService { [Inject] public IObjectPoolService poolService { set; private get; } private AudioMixer mixer; private AudioMixerGroup bgmGroup; private AudioMixerGroup fishingGroup; private AudioMixerGroup uiGroup; private AudioMixerGroup voiceGroup; private AsyncOperationHandle mixerHandle; private AsyncOperationHandle soundPrefabHandle; private Dictionary SoundFadeTaskDic; private float bgmVol = 0; private float fishingVol = 0; private const float Min_Vol = -80.0f; private const float Max_Vol = 20.0f; private const string Master_Vol_Param_Name = "masterVol"; private const string Bgm_Vol_Param_Name = "bgmVol"; private const string Fishing_Vol_Param_Name = "fishingVol"; private const string UI_Vol_Param_Name = "uiVol"; private const string Voice_Vol_Param_Name = "voiceVol"; private const string Lowpass_Cutoff_Frq = "low_cut"; private AudioMixerSnapshot normal; //private AudioMixerSnapshot lowpass; private IDisposable lowpassEvtDisposable; private int _masterVol; public int MasterVol { get { return _masterVol; } set { _masterVol = value; mixer.SetFloat(Master_Vol_Param_Name, VolInt2Float(_masterVol)); } } private int _bgmVol; public int BGMVol { get { return _bgmVol; } set { _bgmVol = value; mixer.SetFloat(Bgm_Vol_Param_Name, VolInt2Float(_bgmVol) + bgmVol); } } private int _sfxVol; public int SFXVol { get { return _sfxVol; } set { _sfxVol = value; mixer.SetFloat(Fishing_Vol_Param_Name, VolInt2Float(_sfxVol) + fishingVol); mixer.SetFloat(UI_Vol_Param_Name, VolInt2Float(_sfxVol)); } } private int _voiceVol; public int VoiceVol { get { return _voiceVol; } set { _voiceVol = value; mixer.SetFloat(Voice_Vol_Param_Name, VolInt2Float(_voiceVol)); } } private float VolInt2Float(int value) { var vol = Mathf.Clamp(value, 0f, 100f) / 100.0f; vol = Min_Vol + (Max_Vol - Min_Vol) * vol; return vol; } private AudioMixerGroup FindMixerGroup(string groupId) { var groups = mixer.FindMatchingGroups(groupId); if (groups != null && groups.Length > 0) return groups[0]; else { Debug.LogWarning("Audio mixer group {groupId} is not found"); return null; } } public async Task InitAsync(string mixerAddr = "AudioMixer", string soundPrefabAddr = "sound", string bgmGroupId = "BGM", string fishingGroupId = "Fishing", string uiGroupId = "UI", string voiceGroupId = "Voice") { SoundFadeTaskDic = new Dictionary(); mixerHandle = Addressables.LoadAssetAsync(mixerAddr); mixer = await mixerHandle.Task; Assert.IsNotNull(mixer); mixer.GetFloat(Bgm_Vol_Param_Name, out bgmVol); mixer.GetFloat(Fishing_Vol_Param_Name, out fishingVol); bgmGroup = FindMixerGroup(bgmGroupId); fishingGroup = FindMixerGroup(fishingGroupId); uiGroup = FindMixerGroup(uiGroupId); voiceGroup = FindMixerGroup(voiceGroupId); normal = mixer.FindSnapshot("Snapshot");// ("normal"); //lowpass = mixer.FindSnapshot("lowpass"); Assert.IsNotNull(bgmGroup); Assert.IsNotNull(fishingGroup); Assert.IsNotNull(uiGroup); soundPrefabHandle = Addressables.LoadAssetAsync(soundPrefabAddr); var soundPrefab = await soundPrefabHandle.Task; Assert.IsNotNull(soundPrefab); var sound = soundPrefab.GetComponent(); Assert.IsNotNull(sound); lowpassEvtDisposable = GContext.OnEvent().Subscribe(OnLowpassEffectEvt); poolService.CreatePool(sound, 10, 50); } private void OnLowpassEffectEvt(LowpassEffectEvt evt) { //if (evt.active) //{ // lowpass.TransitionTo(evt.transTime); // return; //} normal.TransitionTo(evt.transTime); } public void Release() { foreach (var source in SoundFadeTaskDic.Values) { source.Cancel(); } SoundFadeTaskDic.Clear(); SoundFadeTaskDic = null; poolService.DestroyPool(); Addressables.Release(mixerHandle); Addressables.Release(soundPrefabHandle); lowpassEvtDisposable.Dispose(); lowpassEvtDisposable = null; } public void FadeIn(Sound sound, float duration) { if (SoundFadeTaskDic.TryGetValue(sound, out var s)) { s.Cancel(); SoundFadeTaskDic[sound].Dispose(); SoundFadeTaskDic.Remove(sound); } var source = new CancellationTokenSource(); var token = source.Token; SoundFadeTaskDic.Add(sound, source); DoFade(0f, 1f, sound, duration, token); } public void FadeOut(Sound sound, float duration) { if (SoundFadeTaskDic.TryGetValue(sound, out var s)) { s.Cancel(); SoundFadeTaskDic[sound].Dispose(); SoundFadeTaskDic.Remove(sound); } var source = new CancellationTokenSource(); var token = source.Token; SoundFadeTaskDic.Add(sound, source); DoFade(1f, 0f, sound, duration, token); } public Sound GetNewBgmSound(AudioClip clip, bool loop = true, int priority = 0, float time = 0) { var sound = poolService.SpawnObject(); Assert.IsNotNull(sound, "Sound Pool reached max num"); var audioSource = sound.audioSource; audioSource.clip = clip; audioSource.loop = loop; audioSource.priority = priority; audioSource.time = time; audioSource.outputAudioMixerGroup = bgmGroup; audioSource.Pause(); return sound; } public Sound GetNewFishingSound(AudioClip clip, bool loop = false, int priority = 128, float time = 0) { var sound = poolService.SpawnObject(); Assert.IsNotNull(sound, "Sound Pool reached max num"); var audioSource = sound.audioSource; audioSource.clip = clip; audioSource.loop = loop; audioSource.priority = priority; audioSource.time = time; audioSource.outputAudioMixerGroup = fishingGroup; return sound; } public Sound GetNewUISound(AudioClip clip, bool loop = false, int priority = 128, float time = 0) { var sound = poolService.SpawnObject(); Assert.IsNotNull(sound, "Sound Pool reached max num"); var audioSource = sound.audioSource; audioSource.clip = clip; audioSource.loop = loop; audioSource.priority = priority; audioSource.time = time; audioSource.outputAudioMixerGroup = uiGroup; return sound; } public Sound GetNewVoiceSound(AudioClip clip) { var sound = poolService.SpawnObject(); Assert.IsNotNull(sound, "Sound Pool reached max num"); var audioSource = sound.audioSource; audioSource.clip = clip; audioSource.loop = false; audioSource.priority = 128; audioSource.time = 0; audioSource.outputAudioMixerGroup = voiceGroup; return sound; } public void ReturnSound(Sound sound) { if (SoundFadeTaskDic == null || sound == null) return; if (SoundFadeTaskDic.TryGetValue(sound, out var s)) { s.Cancel(); SoundFadeTaskDic[sound].Dispose(); SoundFadeTaskDic.Remove(sound); } if (sound.enabled) { poolService.DespawnObject(sound); } } private async void DoFade(float startVol, float endVol, Sound sound, float duration, CancellationToken ct) { var elapsedTime = 0f; sound.audioSource.volume = startVol; try { while (elapsedTime < duration) { if (ct.IsCancellationRequested) { break; } else { sound.audioSource.volume = Mathf.Lerp(startVol, endVol, elapsedTime / duration); elapsedTime += Time.deltaTime; await Task.Yield(); } } Assert.IsNotNull(sound); sound.audioSource.volume = endVol; } finally { if (!ct.IsCancellationRequested && SoundFadeTaskDic != null) { SoundFadeTaskDic[sound].Dispose(); SoundFadeTaskDic.Remove(sound); } } } } }