From 898ef6f29923fbe87e983e2f3ad2cc9c8bba09db Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:46:34 +0800 Subject: [PATCH 01/13] New ResyncVocals --- source/funkin/game/PlayState.hx | 38 ++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index baad9d40d..54397b5aa 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -559,8 +559,8 @@ class PlayState extends MusicBeatState @:noCompletion @:dox(hide) private var _startCountdownCalled:Bool = false; @:noCompletion @:dox(hide) private var _endSongCalled:Bool = false; - @:dox(hide) - var __vocalSyncTimer:Float = 1; + @:dox(hide) var __vocalSyncTimer:Float = 0; + @:dox(hide) var __vocalOffsetTimer:Float = 0; private function get_accuracy():Float { if (accuracyPressedNotes <= 0) return -1; @@ -1391,17 +1391,39 @@ class PlayState extends MusicBeatState } } else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) { - __vocalSyncTimer = 1; + __vocalSyncTimer = 1 / 30; var instTime = FlxG.sound.music.getActualTime(); - var isOffsync:Bool = vocals.loaded && Math.abs(instTime - vocals.getActualTime()) > 100; - if (!isOffsync) { - for (strumLine in strumLines.members) { - if ((isOffsync = strumLine.vocals.loaded && Math.abs(instTime - strumLine.vocals.getActualTime()) > 100)) break; + + var vocalsLoaded = vocals.loaded; + var vocalTime = vocalsLoaded ? vocals.getActualTime() : instTime; + var offsetTime = instTime - vocalTime; + + for (i in 0...strumLines.members.length) + { + var strumLine = strumLines.members[i]; + var strumVocals = strumLine.vocals; + + if (strumVocals.loaded) + { + var strumVocalTime = strumVocals.getActualTime(); + var currentOffset = instTime - strumVocalTime; + + if (currentOffset * currentOffset > offsetTime * offsetTime) + offsetTime = currentOffset; } } - if (isOffsync) resyncVocals(); + __vocalOffsetTimer += (offsetTime - __vocalOffsetTimer) * __vocalSyncTimer * 60 * 0.1; + + + // abs + if (__vocalOffsetTimer * __vocalOffsetTimer > 100) // +-10ms + { + // trace('ResyncVocals - OffsetTimer: ' + __vocalOffsetTimer); + __vocalOffsetTimer = 0; + resyncVocals(); + } } while(events.length > 0 && events.last().time <= Conductor.songPosition) From 60b6842cc3f8246d5892fc62851e9cb1a5ff8956 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:07:29 +0800 Subject: [PATCH 02/13] Update PlayState.hx --- source/funkin/game/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 54397b5aa..771c0f423 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1418,7 +1418,7 @@ class PlayState extends MusicBeatState // abs - if (__vocalOffsetTimer * __vocalOffsetTimer > 100) // +-10ms + if (__vocalOffsetTimer * __vocalOffsetTimer > 64) // +-8ms { // trace('ResyncVocals - OffsetTimer: ' + __vocalOffsetTimer); __vocalOffsetTimer = 0; From 159225791448a7f96a8676e42144f887f5adc133 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:17:12 +0800 Subject: [PATCH 03/13] Stabilize the detection mechanism. --- source/funkin/game/PlayState.hx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 771c0f423..2e8ba1b03 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1391,7 +1391,8 @@ class PlayState extends MusicBeatState } } else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) { - __vocalSyncTimer = 1 / 30; + __vocalSyncTimer += 1 / 30; + if (__vocalSyncTimer < -1 / 30) __vocalSyncTimer = -1 / 30; var instTime = FlxG.sound.music.getActualTime(); @@ -1414,13 +1415,15 @@ class PlayState extends MusicBeatState } } - __vocalOffsetTimer += (offsetTime - __vocalOffsetTimer) * __vocalSyncTimer * 60 * 0.1; + __vocalOffsetTimer += (offsetTime - __vocalOffsetTimer) * (1 / 30) * 60 * 0.04; + // trace('OffsetTimer: ' + __vocalOffsetTimer + ' - Timer: ' + __vocalSyncTimer); // abs - if (__vocalOffsetTimer * __vocalOffsetTimer > 64) // +-8ms + if (__vocalOffsetTimer * __vocalOffsetTimer > 50) // +-7.071ms { // trace('ResyncVocals - OffsetTimer: ' + __vocalOffsetTimer); + __vocalSyncTimer += 1; __vocalOffsetTimer = 0; resyncVocals(); } From 388670c42fb9a8654a05486d8e019a98a4b3d990 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:54:42 +0800 Subject: [PATCH 04/13] Update PlayState.hx Unless any issues are discovered, this should be the final version of the modifications. --- source/funkin/game/PlayState.hx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 2e8ba1b03..451a36a22 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1417,13 +1417,8 @@ class PlayState extends MusicBeatState __vocalOffsetTimer += (offsetTime - __vocalOffsetTimer) * (1 / 30) * 60 * 0.04; - // trace('OffsetTimer: ' + __vocalOffsetTimer + ' - Timer: ' + __vocalSyncTimer); - - // abs - if (__vocalOffsetTimer * __vocalOffsetTimer > 50) // +-7.071ms + if (__vocalOffsetTimer * __vocalOffsetTimer > 50) // ±7.071ms { - // trace('ResyncVocals - OffsetTimer: ' + __vocalOffsetTimer); - __vocalSyncTimer += 1; __vocalOffsetTimer = 0; resyncVocals(); } From 1c73cfa343c712d4204e9fbc373f65048c1ed76a Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:39:34 +0800 Subject: [PATCH 05/13] Update PlayState.hx Alright, to prevent cases where a sudden large audio offset could cause the smoothed value to instantly exceed the threshold and trigger readjustment, I've made further modifications. I also took the opportunity to tweak a few other things. --- source/funkin/game/PlayState.hx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 451a36a22..9ae50adb8 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -561,6 +561,7 @@ class PlayState extends MusicBeatState @:dox(hide) var __vocalSyncTimer:Float = 0; @:dox(hide) var __vocalOffsetTimer:Float = 0; + @:dox(hide) var __vocalSmoothFactor:Float = 0.05; // (1 / 30) * 60 * 0.025 private function get_accuracy():Float { if (accuracyPressedNotes <= 0) return -1; @@ -1398,7 +1399,7 @@ class PlayState extends MusicBeatState var vocalsLoaded = vocals.loaded; var vocalTime = vocalsLoaded ? vocals.getActualTime() : instTime; - var offsetTime = instTime - vocalTime; + var maxOffset = instTime - vocalTime; for (i in 0...strumLines.members.length) { @@ -1410,12 +1411,14 @@ class PlayState extends MusicBeatState var strumVocalTime = strumVocals.getActualTime(); var currentOffset = instTime - strumVocalTime; - if (currentOffset * currentOffset > offsetTime * offsetTime) - offsetTime = currentOffset; + if (currentOffset * currentOffset > maxOffset * maxOffset) + maxOffset = currentOffset; } } - __vocalOffsetTimer += (offsetTime - __vocalOffsetTimer) * (1 / 30) * 60 * 0.04; + maxOffset = Math.min(Math.max(maxOffset, -40), 40); + + __vocalOffsetTimer += (maxOffset - __vocalOffsetTimer) * __vocalSmoothFactor; if (__vocalOffsetTimer * __vocalOffsetTimer > 50) // ±7.071ms { From 366320298ff429ae59667f5be1fd40076316636a Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:50:31 +0800 Subject: [PATCH 06/13] Additional update --- source/funkin/game/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 9ae50adb8..99a9a79a9 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -561,7 +561,7 @@ class PlayState extends MusicBeatState @:dox(hide) var __vocalSyncTimer:Float = 0; @:dox(hide) var __vocalOffsetTimer:Float = 0; - @:dox(hide) var __vocalSmoothFactor:Float = 0.05; // (1 / 30) * 60 * 0.025 + @:dox(hide) final __vocalSmoothFactor:Float = 0.05; // (1 / 30) * 60 * 0.025 private function get_accuracy():Float { if (accuracyPressedNotes <= 0) return -1; From 431723d1f477eefe1a39647542e0040c25e14d73 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 27 Sep 2025 21:57:43 +0800 Subject: [PATCH 07/13] NEW Resync Vocals --- source/funkin/game/PlayState.hx | 98 ++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 295357a3d..3976d0412 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -556,12 +556,18 @@ class PlayState extends MusicBeatState */ public var hitWindow:Float = Options.hitWindow; // is calculated in create(), is safeFrames in milliseconds. + /** + * Whether or not to use pitch correction when resyncing vocals. + * Without using pitch adjustment, the audio may occasionally exhibit subtle sync drift. + */ + public var usePitchCorrection:Bool = true; + @:noCompletion @:dox(hide) private var _startCountdownCalled:Bool = false; @:noCompletion @:dox(hide) private var _endSongCalled:Bool = false; @:dox(hide) var __vocalSyncTimer:Float = 0; @:dox(hide) var __vocalOffsetTimer:Float = 0; - @:dox(hide) final __vocalSmoothFactor:Float = 0.05; // (1 / 30) * 60 * 0.025 + @:dox(hide) var __sounds:Array>; private function get_accuracy():Float { if (accuracyPressedNotes <= 0) return -1; @@ -1237,6 +1243,29 @@ class PlayState extends MusicBeatState super.onFocusLost(); } + /** + * Call this function whenever you load new sounds + * to make sure the sound list is up to date + */ + public function soundUpdate():Void + { + var sounds:Array> = []; + var idx:Int = 0; + + // add main vocals + if (vocals.loaded) sounds[idx++] = [vocals, 0]; + + // also add strumline vocals + var sl = strumLines.members; + var sln = sl.length; + for (i in 0...sln) { + var sv = sl[i].vocals; + if (sv.loaded) sounds[idx++] = [sv, 0]; // [sound, offset] + } + + __sounds = sounds; // update sound list + } + @:dox(hide) function resyncVocals():Void { @@ -1399,40 +1428,47 @@ class PlayState extends MusicBeatState startSong(); } } - else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) { - __vocalSyncTimer += 1 / 30; - if (__vocalSyncTimer < -1 / 30) __vocalSyncTimer = -1 / 30; - - var instTime = FlxG.sound.music.getActualTime(); - - var vocalsLoaded = vocals.loaded; - var vocalTime = vocalsLoaded ? vocals.getActualTime() : instTime; - var maxOffset = instTime - vocalTime; - - for (i in 0...strumLines.members.length) + else if (FlxG.sound.music != null) + { + if ((__vocalSyncTimer -= elapsed) <= 0) { - var strumLine = strumLines.members[i]; - var strumVocals = strumLine.vocals; - - if (strumVocals.loaded) - { - var strumVocalTime = strumVocals.getActualTime(); - var currentOffset = instTime - strumVocalTime; + __vocalSyncTimer = (__vocalSyncTimer < -0.1) ? -0.1 : __vocalSyncTimer + 0.1; // max 10fps - if (currentOffset * currentOffset > maxOffset * maxOffset) - maxOffset = currentOffset; + if (__sounds != null) { + final soundCount = __sounds.length; + if (soundCount > 0) + { + final mt = FlxG.sound.music.getActualTime(); // in ms + final vs = usePitchCorrection ? 2500 : 225; // 15ms for no pitch correction, 50ms for pitch correction + final pf = 0.00025; // pitch factor + final sm = 0.1; // smoothing + + // account for offset changes + var i = soundCount; + while (i-- > 0) + { + var sd:Array = __sounds[i]; + var s:FlxSound = sd[0]; + final ct = s.getActualTime(); + + final diff = mt - ct; + sd[1] += (diff - sd[1]) * sm; // smooth the difference + + if (usePitchCorrection) s.pitch = 1 + sd[1] * pf; // pitch adjustment + trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); + + final os = sd[1]; + if (os * os > vs) + { + sd[1] = 0; + s.play(true, Conductor.songPosition); // restart sound at music position + } + } + } + } else { + soundUpdate(); } } - - maxOffset = Math.min(Math.max(maxOffset, -40), 40); - - __vocalOffsetTimer += (maxOffset - __vocalOffsetTimer) * __vocalSmoothFactor; - - if (__vocalOffsetTimer * __vocalOffsetTimer > 50) // ±7.071ms - { - __vocalOffsetTimer = 0; - resyncVocals(); - } } while(events.length > 0 && events.last().time <= Conductor.songPosition) From 131dc09c672b3a181b2baf160bbaf109903134f4 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 27 Sep 2025 22:25:10 +0800 Subject: [PATCH 08/13] Update PlayState.hx --- source/funkin/game/PlayState.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 3976d0412..6e01038fa 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1455,7 +1455,6 @@ class PlayState extends MusicBeatState sd[1] += (diff - sd[1]) * sm; // smooth the difference if (usePitchCorrection) s.pitch = 1 + sd[1] * pf; // pitch adjustment - trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); final os = sd[1]; if (os * os > vs) From 9dec8798e1fcef52b137c818af2a7600db8f2aa0 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 27 Sep 2025 22:28:58 +0800 Subject: [PATCH 09/13] eee --- source/funkin/game/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 6e01038fa..fd686c0c3 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1439,7 +1439,7 @@ class PlayState extends MusicBeatState if (soundCount > 0) { final mt = FlxG.sound.music.getActualTime(); // in ms - final vs = usePitchCorrection ? 2500 : 225; // 15ms for no pitch correction, 50ms for pitch correction + final vs = usePitchCorrection ? 625 : 144; // 12ms for no pitch correction, 25ms for pitch correction final pf = 0.00025; // pitch factor final sm = 0.1; // smoothing From 34c5c1e6cec4fa1f75f078262022eefca31efb3d Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 27 Sep 2025 22:53:26 +0800 Subject: [PATCH 10/13] Combining Two Synchronization Methods Using time-stretching for minor corrections (subtle drift), and hard playback resets for high-latency scenarios. This approach minimizes audible pitch distortion while maintaining high-precision audio playback. --- source/funkin/game/PlayState.hx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index fd686c0c3..92413101f 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1256,10 +1256,11 @@ class PlayState extends MusicBeatState if (vocals.loaded) sounds[idx++] = [vocals, 0]; // also add strumline vocals - var sl = strumLines.members; - var sln = sl.length; - for (i in 0...sln) { - var sv = sl[i].vocals; + final sl = strumLines.members; + final sln = sl.length; + for (i in 0...sln) + { + final sv = sl[i].vocals; if (sv.loaded) sounds[idx++] = [sv, 0]; // [sound, offset] } @@ -1269,11 +1270,17 @@ class PlayState extends MusicBeatState @:dox(hide) function resyncVocals():Void { - var time = Conductor.songPosition + Conductor.songOffset; + final time = Conductor.songPosition + Conductor.songOffset; for (strumLine in strumLines.members) strumLine.vocals.play(true, time); vocals.play(true, time); if (!inst.playing) inst.play(true, time); + var sounds = __sounds; + var sln = sounds.length; + for (i in 0...sln) + sounds[i][1] = 0; + + gameAndCharsCall("onVocalsResync"); } @@ -1439,7 +1446,7 @@ class PlayState extends MusicBeatState if (soundCount > 0) { final mt = FlxG.sound.music.getActualTime(); // in ms - final vs = usePitchCorrection ? 625 : 144; // 12ms for no pitch correction, 25ms for pitch correction + final vs = usePitchCorrection ? 256 : 100; // 10ms for no pitch correction, 16ms for pitch correction final pf = 0.00025; // pitch factor final sm = 0.1; // smoothing @@ -1462,6 +1469,7 @@ class PlayState extends MusicBeatState sd[1] = 0; s.play(true, Conductor.songPosition); // restart sound at music position } + trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); } } } else { From dcd8220b91ad5db0960627cccb162c7560f65357 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:25:17 +0800 Subject: [PATCH 11/13] =?UTF-8?q?trace=20=F0=9F=98=B5=20=20=F0=9F=98=B5=20?= =?UTF-8?q?=20=F0=9F=98=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/funkin/game/PlayState.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 92413101f..f65bce049 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1469,7 +1469,6 @@ class PlayState extends MusicBeatState sd[1] = 0; s.play(true, Conductor.songPosition); // restart sound at music position } - trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); } } } else { From 16c90c1023460203ed3e4fa96ee51fa21714cebe Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:00:38 +0800 Subject: [PATCH 12/13] Update PlayState.hx --- source/funkin/game/PlayState.hx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index f65bce049..5db3ad60c 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1435,7 +1435,7 @@ class PlayState extends MusicBeatState startSong(); } } - else if (FlxG.sound.music != null) + else if (FlxG.sound.music != null && FlxG.sound.music.playing) { if ((__vocalSyncTimer -= elapsed) <= 0) { @@ -1456,6 +1456,8 @@ class PlayState extends MusicBeatState { var sd:Array = __sounds[i]; var s:FlxSound = sd[0]; + if (!s.playing) continue; + final ct = s.getActualTime(); final diff = mt - ct; @@ -1468,6 +1470,7 @@ class PlayState extends MusicBeatState { sd[1] = 0; s.play(true, Conductor.songPosition); // restart sound at music position + trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); } } } From fb3278a7cbea7b8534e9cff5b8148556f0c0e11a Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:20:13 +0800 Subject: [PATCH 13/13] Update PlayState.hx --- source/funkin/game/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 5db3ad60c..afa430cd0 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1470,8 +1470,8 @@ class PlayState extends MusicBeatState { sd[1] = 0; s.play(true, Conductor.songPosition); // restart sound at music position - trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); } + trace('Sound ' + i + ': music=' + mt + ', sound=' + ct + ', diff=' + diff + ', smoothDiff=' + sd[1] + ', pitch=' + s.pitch); } } } else {