From c609fb3550502a35c1d50ef6c66ae5597da911b3 Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Fri, 11 Jul 2025 23:28:48 +0200 Subject: [PATCH] Attempt at fixing the beat desync --- MuzikaGromche/Plugin.cs | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/MuzikaGromche/Plugin.cs b/MuzikaGromche/Plugin.cs index 9b4ef28..1458261 100644 --- a/MuzikaGromche/Plugin.cs +++ b/MuzikaGromche/Plugin.cs @@ -55,10 +55,12 @@ namespace MuzikaGromche public static Coroutine JesterLightSwitching; public static Track CurrentTrack; - public static void StartLightSwitching(MonoBehaviour __instance) + public static void StartLightSwitching(JesterAI __instance) { StopLightSwitching(__instance); - JesterLightSwitching = __instance.StartCoroutine(RotateColors()); + var preStart = Plugin.CurrentTrack.LoadedStart.length - Plugin.CurrentTrack.WindUpTimer; + var loopSource = __instance.creatureVoice; + JesterLightSwitching = __instance.StartCoroutine(RotateColors(preStart, loopSource)); } public static void StopLightSwitching(MonoBehaviour __instance) @@ -86,10 +88,13 @@ namespace MuzikaGromche // TODO: Move to Track class to make them customizable per-song static List colors = [Color.magenta, Color.cyan, Color.green, Color.yellow]; - public static IEnumerator RotateColors() + public static IEnumerator RotateColors(float preStart, AudioSource loopSource) { Debug.Log("Starting color rotation"); var i = 0; + var lastSwitch = Time.realtimeSinceStartup; + var pause = 60f / CurrentTrack.Bpm; + Debug.Log("Prestart: " + preStart); while (true) { var color = colors[i]; @@ -98,7 +103,23 @@ namespace MuzikaGromche i = (i + 1) % colors.Count; if (CurrentTrack != null) { - yield return new WaitForSeconds(60f / CurrentTrack.Bpm); + if (loopSource.time <= 0) + { + // if (possibly) delayed but not yet started + var newSwitch = Time.realtimeSinceStartup; + var toSleep = Math.Max(pause - (newSwitch - lastSwitch), 0); + lastSwitch = newSwitch; + Debug.Log("Prestart sleep " + toSleep); + yield return new WaitForSeconds(toSleep); + } + else + { + var passedSincePop = loopSource.time + preStart; + var toSleep = pause - passedSincePop % pause; + Debug.Log($"Loop sleep {toSleep}, loop time {loopSource.time}"); + if (toSleep < pause / 2) toSleep += pause; + yield return new WaitForSeconds(toSleep); + } } else { @@ -147,8 +168,6 @@ namespace MuzikaGromche // from the looped part. This also means that the light show starts before // the looped track does, so we need to sync them up as soon as we enter the Loop. public float WindUpTimer; - // BPM for light switching in sync with the music. There is no offset, - // so the Loop track should start precisely on a beat. public float Bpm; // MPEG is basically mp3, and it can produce gaps at the start. @@ -251,8 +270,9 @@ namespace MuzikaGromche if (__instance.previousState == 2 && !__instance.creatureVoice.isPlaying) { __instance.creatureVoice.maxDistance = 150; - __instance.creatureVoice.clip = Plugin.CurrentTrack.LoadedLoop; - var time = __instance.farAudio.time; + __instance.creatureVoice.clip = Plugin.CurrentTrack.LoadedLoop; // creature voice eto loop + + var time = __instance.farAudio.time; // far audio eto start var delay = Plugin.CurrentTrack.LoadedStart.length - time; Debug.Log($"Start length: {Plugin.CurrentTrack.LoadedStart.length}; played time: {time}"); Debug.Log($"Playing loop music: maxDistance: {__instance.creatureVoice.maxDistance}, minDistance: {__instance.creatureVoice.minDistance}, volume: {__instance.creatureVoice.volume}, spread: {__instance.creatureVoice.spread}, in seconds: {delay}");