Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
898ef6f
New ResyncVocals
HEIHUAa Sep 9, 2025
60b6842
Update PlayState.hx
HEIHUAa Sep 9, 2025
1592257
Stabilize the detection mechanism.
HEIHUAa Sep 9, 2025
388670c
Update PlayState.hx
HEIHUAa Sep 9, 2025
e1bc7a0
Merge branch 'CodenameCrew:main' into main
HEIHUAa Sep 11, 2025
1c73cfa
Update PlayState.hx
HEIHUAa Sep 12, 2025
3663202
Additional update
HEIHUAa Sep 12, 2025
bbbb107
Merge branch 'CodenameCrew:main' into main
HEIHUAa Sep 21, 2025
1258d76
Merge branch 'CodenameCrew:main' into main
HEIHUAa Sep 26, 2025
431723d
NEW Resync Vocals
HEIHUAa Sep 27, 2025
131dc09
Update PlayState.hx
HEIHUAa Sep 27, 2025
9dec879
eee
HEIHUAa Sep 27, 2025
34c5c1e
Combining Two Synchronization Methods
HEIHUAa Sep 27, 2025
dcd8220
trace 😵 😵 😵
HEIHUAa Sep 27, 2025
16c90c1
Update PlayState.hx
HEIHUAa Sep 28, 2025
fb3278a
Update PlayState.hx
HEIHUAa Sep 28, 2025
42718c5
Merge branch 'CodenameCrew:main' into main
HEIHUAa Oct 9, 2025
36e75ff
Merge branch 'CodenameCrew:main' into main
HEIHUAa Oct 24, 2025
0162a1d
Merge branch 'CodenameCrew:main' into main
HEIHUAa Oct 25, 2025
39993f3
FlxG.sound.music.getActualTime()
HEIHUAa Oct 27, 2025
40ba57e
Update PlayState.hx
HEIHUAa Oct 27, 2025
443a496
0.9
HEIHUAa Oct 27, 2025
07a5416
Add vocal sync interval and pitch correction flags
HEIHUAa Oct 28, 2025
6e19d32
Remove __vocalOffsetTimer
HEIHUAa Oct 28, 2025
99a03f2
Improve vocal sync interval handling and docs
HEIHUAa Oct 28, 2025
86d2c07
Merge branch 'CodenameCrew:main' into main
HEIHUAa Oct 29, 2025
cead367
Update Flags.hx
HEIHUAa Oct 29, 2025
cf0df4f
Better Algorithm
HEIHUAa Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions source/funkin/backend/system/Flags.hx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ class Flags {
public static var GITAROO_CHANCE:Float = 0.1;
public static var DEFAULT_MUTE_VOCALS_ON_MISS:Bool = true;

/**
* Whether or not to use pitch correction when resyncing vocals.
* Without using pitch adjustment, the audio may occasionally exhibit subtle sync drift.
* If you just want to adjust the overall playback speed, you can try modifying FlxG.timeScale.
*/
public static var VOCAL_PITCH_CORRECTION:Bool = true;
/**
* Interval (in seconds) for vocal synchronization updates.
* Smaller values mean more frequent synchronization but higher CPU usage.
*/
public static var VOCAL_SYNC_INTERVAL:Float = 0.05;

public static var DEFAULT_MAX_HEALTH:Float = 2.0;
public static var DEFAULT_HEALTH:Null<Float> = null;//DEFAULT_MAX_HEALTH / 2.0;
public static var DEFAULT_ICONBOP:Bool = true;
Expand Down
104 changes: 91 additions & 13 deletions source/funkin/game/PlayState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -556,11 +556,20 @@ 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.
* If you just want to adjust the overall playback speed, you can try modifying FlxG.timeScale.
*/
public var usePitchCorrection:Bool = Flags.VOCAL_PITCH_CORRECTION;

@: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 __vocalIntervalMoment:Float = 0.1; // moment for interval updates
@:dox(hide) var __vocalSyncTimer:Float = 0.1;
@:dox(hide) var __vocalSound:Int = 0;
@:dox(hide) var __sounds:Array<Array<Dynamic>>;

private function get_accuracy():Float {
if (accuracyPressedNotes <= 0) return -1;
Expand Down Expand Up @@ -1236,14 +1245,51 @@ 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<Array<Dynamic>> = [];
var idx:Int = 0;

// add main vocals
if (vocals.loaded) sounds[idx++] = [vocals, 0];

// also add strumline 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]
}

if (sounds.length < 1)
{
__sounds = []; // no sounds
return;
}

__sounds = sounds; // update sound list
__vocalIntervalMoment = Flags.VOCAL_SYNC_INTERVAL / sounds.length; // reset interval moment
}

@: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");
}

Expand Down Expand Up @@ -1398,18 +1444,50 @@ class PlayState extends MusicBeatState
startSong();
}
}
else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) {
__vocalSyncTimer = 1;

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;
else if (FlxG.sound.music != null && FlxG.sound.music.playing)
{
if ((__vocalSyncTimer -= elapsed) <= 0)
{
__vocalSyncTimer = (__vocalSyncTimer < -__vocalIntervalMoment) ? 0 : __vocalSyncTimer + __vocalIntervalMoment; // max 10fps

if (__sounds != null) {
final soundCount = __sounds.length;
if (soundCount > 0)
{
final mt = FlxG.sound.music.getActualTime(); // in ms
final vs = usePitchCorrection ? 256 : 100; // 10ms for no pitch correction, 16ms for pitch correction
final pf = 0.00025; // pitch factor
final sm = Flags.VOCAL_SYNC_INTERVAL; // smoothing

// account for offset changes
final i = __vocalSound;

var sd:Array<Dynamic> = __sounds[i];
var s:FlxSound = sd[0];
if (s.playing) {
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

final os = sd[1];
if (os * os > vs)
{
sd[1] = 0;
s.play(true, mt); // restart sound at music position
}
trace('Sound ' + i + ': music=' + Math.round(mt) + ' time=' + Math.round(ct) + ' diff=' + Math.round(diff * 100) / 100 + ' smoothDiff=' + Math.round(sd[1] * 100) / 100 + ' pitch=' + Math.round(s.pitch * 100000) / 100000);

__vocalSound = i + 1 >= soundCount ? 0 : i; // next sound
}
}
} else {
soundUpdate();
__vocalSyncTimer = 1; // next update in 1 seconds
}
}

if (isOffsync) resyncVocals();
}

while(events.length > 0 && events.last().time <= Conductor.songPosition)
Expand Down