diff --git a/Assets/Scenes/PersistantScene.unity b/Assets/Scenes/PersistantScene.unity index 3155ac28c..eb6da258c 100644 --- a/Assets/Scenes/PersistantScene.unity +++ b/Assets/Scenes/PersistantScene.unity @@ -1861,6 +1861,7 @@ GameObject: m_Component: - component: {fileID: 788257160} - component: {fileID: 788257161} + - component: {fileID: 788257162} m_Layer: 0 m_Name: Game Manager m_TagString: Untagged @@ -1896,6 +1897,18 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: k__BackingField: {fileID: 0} +--- !u!114 &788257162 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 788257159} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9528aed6905f440386374733309d1614, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &826511794 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Script/Audio/Bass/BassAudioManager.cs b/Assets/Script/Audio/Bass/BassAudioManager.cs index fa8b62bc3..bc9dc5647 100644 --- a/Assets/Script/Audio/Bass/BassAudioManager.cs +++ b/Assets/Script/Audio/Bass/BassAudioManager.cs @@ -31,6 +31,8 @@ public class BassAudioManager : MonoBehaviour, IAudioManager { public float CurrentPositionF => (float) GetPosition(); public float AudioLengthF { get; private set; } + private IAudioManager.SongEndCallback _songEndCallback; + private double[] _stemVolumes; private ISampleChannel[] _sfxSamples; @@ -38,6 +40,7 @@ public class BassAudioManager : MonoBehaviour, IAudioManager { private IStemMixer _mixer; + private void Awake() { SupportedFormats = new[] { ".ogg", @@ -72,7 +75,10 @@ public void Initialize() { Bass.Configure(Configuration.UpdateThreads, 2); Bass.Configure(Configuration.FloatDSP, true); + // Undocumented BASS_CONFIG_MP3_OLDGAPS config. Bass.Configure((Configuration) 68, 1); + + // Disable undocumented BASS_CONFIG_DEV_TIMEOUT config. Prevents pausing audio output if a device times out. Bass.Configure((Configuration) 70, false); int deviceCount = Bass.DeviceCount; @@ -225,6 +231,10 @@ public void LoadSong(ICollection stems, float speed, params SongStem[] i AudioLengthD = _mixer.LeadChannel.LengthD; AudioLengthF = (float) AudioLengthD; + _mixer.SetSync(SyncFlags.End, () => { + UnityMainThreadCallback.QueueEvent(_songEndCallback.Invoke); + }); + IsAudioLoaded = true; } @@ -307,6 +317,10 @@ public void LoadMogg(ExtractedConSongEntry exConSong, float speed, params SongSt AudioLengthD = _mixer.LeadChannel.LengthD; AudioLengthF = (float) AudioLengthD; + _mixer.SetSync(SyncFlags.End, () => { + UnityMainThreadCallback.QueueEvent(_songEndCallback.Invoke); + }); + IsAudioLoaded = true; } @@ -388,6 +402,14 @@ public void Pause() { IsPlaying = _mixer.IsPlaying; } + public void AddSongEndCallback(IAudioManager.SongEndCallback callback) { + _songEndCallback += callback; + } + + public void RemoveSongEndCallback(IAudioManager.SongEndCallback callback) { + _songEndCallback -= callback; + } + public void FadeIn(float maxVolume) { Play(true); if (IsPlaying) { diff --git a/Assets/Script/Audio/Bass/BassStemMixer.cs b/Assets/Script/Audio/Bass/BassStemMixer.cs index 4108f7b8a..bdf6ae2a3 100644 --- a/Assets/Script/Audio/Bass/BassStemMixer.cs +++ b/Assets/Script/Audio/Bass/BassStemMixer.cs @@ -11,9 +11,9 @@ namespace YARG.Audio.BASS { public class BassStemMixer : IStemMixer { public int StemsLoaded { get; protected set; } - + public bool IsPlaying { get; protected set; } - + public IReadOnlyDictionary Channels => _channels; public IStemChannel LeadChannel { get; protected set; } @@ -175,6 +175,12 @@ public IStemChannel GetChannel(SongStem stem) { return !_channels.ContainsKey(stem) ? null : _channels[stem]; } + public void SetSync(SyncFlags sync, Action callback) { + BassMix.ChannelSetSync(((BassStemChannel) LeadChannel).StreamHandle, sync, 0, (_, _, _, _) => { + UnityMainThreadCallback.QueueEvent(callback); + }, IntPtr.Zero); + } + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); @@ -206,7 +212,7 @@ protected virtual void ReleaseUnmanagedResources() { if (!Bass.StreamFree(_mixerHandle)) { Debug.LogError("Failed to free mixer stream. THIS WILL LEAK MEMORY!"); } - + _mixerHandle = 0; } } diff --git a/Assets/Script/Audio/Interfaces/IAudioManager.cs b/Assets/Script/Audio/Interfaces/IAudioManager.cs index fe0b426c3..f45b41153 100644 --- a/Assets/Script/Audio/Interfaces/IAudioManager.cs +++ b/Assets/Script/Audio/Interfaces/IAudioManager.cs @@ -5,6 +5,9 @@ namespace YARG.Audio { public interface IAudioManager { + + public delegate void SongEndCallback(); + public bool UseStarpowerFx { get; set; } public bool IsChipmunkSpeedup { get; set; } @@ -38,6 +41,9 @@ public interface IAudioManager { public void Play(); public void Pause(); + public void AddSongEndCallback(SongEndCallback callback); + public void RemoveSongEndCallback(SongEndCallback callback); + public void FadeIn(float maxVolume); public UniTask FadeOut(CancellationToken token = default); diff --git a/Assets/Script/Audio/Interfaces/IStemChannel.cs b/Assets/Script/Audio/Interfaces/IStemChannel.cs index e35b56957..9e50125d2 100644 --- a/Assets/Script/Audio/Interfaces/IStemChannel.cs +++ b/Assets/Script/Audio/Interfaces/IStemChannel.cs @@ -21,7 +21,7 @@ public interface IStemChannel : IDisposable { public double GetPosition(); public void SetPosition(double position); - + public double GetLengthInSeconds(); } diff --git a/Assets/Script/Audio/Interfaces/IStemMixer.cs b/Assets/Script/Audio/Interfaces/IStemMixer.cs index 5a0981cee..78de50765 100644 --- a/Assets/Script/Audio/Interfaces/IStemMixer.cs +++ b/Assets/Script/Audio/Interfaces/IStemMixer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; +using ManagedBass; namespace YARG.Audio { public interface IStemMixer : IDisposable { @@ -28,10 +29,12 @@ public interface IStemMixer : IDisposable { public void SetPosition(double position); public int AddChannel(IStemChannel channel); - + public bool RemoveChannel(IStemChannel channel); public IStemChannel GetChannel(SongStem stem); + public void SetSync(SyncFlags sync, Action callback); + } } diff --git a/Assets/Script/PlayMode/Play.cs b/Assets/Script/PlayMode/Play.cs index 1e4a56888..60eafeb37 100644 --- a/Assets/Script/PlayMode/Play.cs +++ b/Assets/Script/PlayMode/Play.cs @@ -329,6 +329,8 @@ private IEnumerator StartAudio() { } GameManager.AudioManager.Play(); + + GameManager.AudioManager.AddSongEndCallback(OnEndReached); audioStarted = true; } @@ -431,12 +433,11 @@ private void Update() { if (!playingVocals) { UpdateGenericLyrics(); } + } - // End song - if (!endReached && realSongTime >= SongLength) { - endReached = true; - StartCoroutine(EndSong(true)); - } + private void OnEndReached() { + endReached = true; + StartCoroutine(EndSong(true)); } private void UpdateGenericLyrics() { diff --git a/Assets/Script/UnityMainThreadCallback.cs b/Assets/Script/UnityMainThreadCallback.cs new file mode 100644 index 000000000..a0d423031 --- /dev/null +++ b/Assets/Script/UnityMainThreadCallback.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace YARG { + public class UnityMainThreadCallback : MonoBehaviour { + + private static readonly Queue CallbackQueue = new(); + + private void Update() { + lock (CallbackQueue) { + while (CallbackQueue.Count > 0) { + CallbackQueue.Dequeue().Invoke(); + } + } + } + + public static void QueueEvent(Action action) { + lock (CallbackQueue) { + CallbackQueue.Enqueue(action); + } + } + } +} \ No newline at end of file diff --git a/Assets/Script/UnityMainThreadCallback.cs.meta b/Assets/Script/UnityMainThreadCallback.cs.meta new file mode 100644 index 000000000..7bb6678a0 --- /dev/null +++ b/Assets/Script/UnityMainThreadCallback.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9528aed6905f440386374733309d1614 +timeCreated: 1686439596 \ No newline at end of file