Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make note rendering based on frames AND song position #3544

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from

Conversation

KutikiPlayz
Copy link

@KutikiPlayz KutikiPlayz commented Oct 3, 2024

Note positions are updated based on the songPosition, songPosition isn't updated every frame, and thus the notes seem to lag on higher framerates.
What this PR does, is adds a new method to Conductor that grabs the songPosition and adds a new variable songPositionDelta to it to create a more accurate songPosition.
Essentially doing what this pr did, without the worry of desync.

This is based on how Minecraft does it's rendering at higher framerates, where instead of everything rendering at 20 fps (since the game runs at 20 ticks per second), they render things at the current tick + the time between that tick and the next tick.

Closes #3127

Here's a vid of Blazin':

balls.mp4

And here's the same vid slowed down to x0.25 speed so people on 60hz monitors can actually see a difference:

balls.slow.mp4

@trayfellow
Copy link

This issue has been bugging me for a rly long time as a 120hz player, thanks!

Copy link
Contributor

@Burgerballs Burgerballs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Burgerballs approves of this!

@nebulazorua
Copy link
Contributor

I approve of this
It's what we do in Troll Engine and Stepmania does something very similar

@EliteMasterEric
Copy link
Member

I can (kinda?) see the problem you're trying to solve, but this is definitely a janky solution for it.

The proper fix is to just improve the code for retrieving the songPosition such that the value is more accurate.

@EliteMasterEric EliteMasterEric added status: needs revision Cannot be approved because it is awaiting some work by the contributor. type: optimization Involves a performance issue or a bug which causes lag. labels Oct 3, 2024
@KutikiPlayz
Copy link
Author

KutikiPlayz commented Oct 3, 2024

It is a janky solution, but I couldn't think of anything for fixing songPosition itself so this is the best I've got.

As far as I'm aware this doesn't have any issues with it besides the fact it doesn't solve the actual problem at hand, but I feel like if Stepmania does something similar as nebula said, then it can't be that terrible of a thing to do.

There's a point in which a problem becomes too complicated to solve and it's just easier to do a band-aid fix, especially when the only thing it effects is the note rendering.

@lemz1
Copy link
Contributor

lemz1 commented Oct 4, 2024

I don't think we can do anything about getting a more accurate time for songPosition. We would need to look into openAL or lime.NativeAudioSource

lime._internal.backend.native.NativeAudioSource.hx:

else
{
  // var offset = AL.getSourcei(handle, AL.BYTE_OFFSET);
  // var ratio = (offset / dataLength);
  // var totalSeconds = samples / parent.buffer.sampleRate;
  
  // var sampleTime = AL.getSourcef(handle, AL.SAMPLE_OFFSET);
  // var time = (sampleTime / parent.buffer.sampleRate * 1000) -
  
  // var time = Std.int(totalSeconds * ratio * 1000) - parent.offset;
  // var time = Std.int (AL.getSourcef (handle, AL.SEC_OFFSET) * 1000) - parent.offset;
  
  var value = AL.getSourcedvSOFT(handle, AL.SEC_OFFSET_LATENCY_SOFT, 2);
  var deviceOffset:Float = value[1];
  var realOffset:Float = value[0];
  var time:Float = ((realOffset - deviceOffset) * 1000) - parent.offset;
  
  if (time < 0) return 0;
  return Std.int(time);
}

that is the function for retriving the songPosition. I dont know what getSourcedvSOFT does exactly, the only thing i know is that (atleast for me) the songPosition updates every 10ms, meaning 100fps

i'll see if getSourcef is better, but i doubt it

Im not sure if OpenAL supports setting the update interval, maybe someone knows that?

@KutikiPlayz
Copy link
Author

KutikiPlayz commented Oct 4, 2024

yeah I feel like if it could update faster they would've done it already

there could be a solution somewhere but it requires so much digging and learning how all that stuff works, it just doesn't seem worth it

@ninjamuffin99 ninjamuffin99 force-pushed the develop branch 2 times, most recently from e0b1b01 to 410cfe9 Compare October 4, 2024 01:25
@ninjamuffin99
Copy link
Member

I don't think it's janky, it seems pretty clean and simple implementation!

  • If songPosition is updated when we call Conductor.update(), we use that value, which is the songPosition value we've always used
  • If songPosition isn't updated, the frameSongPosition is incremented by delta time (FlxG.elapsed), essentially making an assumption that if songPosition were to be updated that frame, it would be about at that time.
  • With rhythm games you obviously want to use the music time to keep in time, so incrementing is usually not the way to go, but I think we get the actual songPosition updated frequently enough that there won't be any drift occuring that lasts more than, maybe a frame or two, which would purely be visual, since we use a different system for getting accurate inputs.

Copy link
Contributor

@cyn0x8 cyn0x8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay

@ninjamuffin99 ninjamuffin99 force-pushed the frame-song-position-for-note-rendering branch from e1d6bd8 to 8e57ba1 Compare October 4, 2024 02:40
@ninjamuffin99
Copy link
Member

just rebased / cleaned up the commit history here to match current develop branch 🤝

@KutikiPlayz
Copy link
Author

guys github is so confusing why is there magically 38 commits again

@ninjamuffin99 ninjamuffin99 force-pushed the frame-song-position-for-note-rendering branch from 3330cd3 to 8e57ba1 Compare October 4, 2024 03:38
@ninjamuffin99
Copy link
Member

you may have pushed your local "changes" which would have been the old commit history with all of the merge commits and whatnot. If you're using command line, you can type (as long as you dont have any local changes you ACTUALLY want to make)

git fetch origin
git reset --hard origin/frame-song-position-for-note-rendering

and then your git history on your computer will match the one i very evilly force pushed

@KutikiPlayz
Copy link
Author

thank you mr muffin
I will just simply not touch this again as to make sure I don't break anything

@nebulazorua
Copy link
Contributor

nebulazorua commented Oct 4, 2024

I can (kinda?) see the problem you're trying to solve, but this is definitely a janky solution for it.

The proper fix is to just improve the code for retrieving the songPosition such that the value is more accurate.

Don't know how much better you CAN get though, given audio clock n all. Unless flixel adds something similar to Unity's dspTime this is probably the best we're gonna get.

The method used here is used in Stepmania and all of it's forks (including highly competitive ones like Etterna) because it simply.. Works.
(I think Quaver does something similar, too?)

@Burgerballs
Copy link
Contributor

Burgerballs commented Oct 4, 2024

I can (kinda?) see the problem you're trying to solve, but this is definitely a janky solution for it.

The proper fix is to just improve the code for retrieving the songPosition such that the value is more accurate.

tbh this "janky" solution is the only one i can think of, its actually really effective in how it works, and the "proper fix" you are eluding to here isnt really achievable unless if you change the audio system flixel or lime itself.

for that i'd argue it isnt really that janky at all and the audio clock stuff we'd have to do otherwise would be infinitely jankier, and it would not fix the note rendering issue at all because the position updating would still be independent to the framerate, and doing any other method would just loop back to doing FlxG.elapsed * 1000 but even more complicated

stepmania, openitg, notitg, etterna, project outfox and osu!lazer do it the same way so finding any other method to do so is a fool's errand

@ninjamuffin99
Copy link
Member

I think an alternative off the top of my own head is to use the Conductor.getTimeWithDiff(), which is what we use to timestamp and get a more accurate song position for INPUTS (which works if you call it in-between frames as well). If using that provides a similar result to using this PR's framePosition stuff, then I think getTimeWithDiff() might be optimal, so we don't have two functions/variables that do nearly the same thing

@lemz1
Copy link
Contributor

lemz1 commented Oct 4, 2024

I think an alternative off the top of my own head is to use the Conductor.getTimeWithDiff(), which is what we use to timestamp and get a more accurate song position for INPUTS (which works if you call it in-between frames as well). If using that provides a similar result to using this PR's framePosition stuff, then I think getTimeWithDiff() might be optimal, so we don't have two functions/variables that do nearly the same thing

using getTimeWithDiff won't change anything, since _channel.position is frame-independent. In my case its 10ms, meaning it doesn't matter when or where you call it, it will always use the 10ms as a check for updating the sound.

I logged getTimeWithDiff in the update function of PlayState:

source/funkin/play/PlayState.hx:837: TIME: 1166
source/funkin/play/PlayState.hx:837: TIME: 1176
source/funkin/play/PlayState.hx:837: TIME: 1186
source/funkin/play/PlayState.hx:837: TIME: 1196
source/funkin/play/PlayState.hx:837: TIME: 1196
source/funkin/play/PlayState.hx:837: TIME: 1206
source/funkin/play/PlayState.hx:837: TIME: 1216
source/funkin/play/PlayState.hx:837: TIME: 1226
source/funkin/play/PlayState.hx:837: TIME: 1236
source/funkin/play/PlayState.hx:837: TIME: 1236
source/funkin/play/PlayState.hx:837: TIME: 1246
source/funkin/play/PlayState.hx:837: TIME: 1256
source/funkin/play/PlayState.hx:837: TIME: 1266
source/funkin/play/PlayState.hx:837: TIME: 1276
source/funkin/play/PlayState.hx:837: TIME: 1286

@lemz1
Copy link
Contributor

lemz1 commented Oct 4, 2024

@KutikiPlayz one question though, don't we need to apply this:

songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0;

to frameSongPos, or am i wrong?

@KutikiPlayz
Copy link
Author

KutikiPlayz commented Oct 4, 2024

see I was thinking about that but the offsets are added directly to songPosition itself, and since frameSongPosition increments itself we don’t wanna add the offsets to it over and over again, it should just get the offsets when it syncs with songPosition

@KutikiPlayz
Copy link
Author

I think an alternative off the top of my own head is to use the Conductor.getTimeWithDiff(), which is what we use to timestamp and get a more accurate song position for INPUTS (which works if you call it in-between frames as well). If using that provides a similar result to using this PR's framePosition stuff, then I think getTimeWithDiff() might be optimal, so we don't have two functions/variables that do nearly the same thing

I’ll test it with getTimeWithDiff() and see what happens

@github-actions github-actions bot added size: medium A medium pull request with 100 or fewer changes. pr: haxe PR modifies game code. and removed size: medium A medium pull request with 100 or fewer changes. labels Oct 12, 2024
Copy link
Contributor

@doggogit doggogit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I managed to gh checkout this PR into my cloned Funkin repo on my machine to test the changes.

As a 144Hz player, on both PC and laptop, this is a really welcoming change! Good shit!

@AbnormalPoof
Copy link
Collaborator

AbnormalPoof commented Oct 17, 2024

Don't think. Just merge

They still need to review this internally first, they can't just merge PRs left and right.
They'll get to this PR eventually.

@biomseed
Copy link

They still need to review this internally first, they can't just merge PRs left and right. They'll get to this PR eventually.

If they were reviewing it internally they would have given it that tag. This one has "needs revision".

@github-actions github-actions bot added size: medium A medium pull request with 100 or fewer changes. and removed size: medium A medium pull request with 100 or fewer changes. labels Nov 27, 2024
based on minecraft's tickDelta variable they use for smoother rendering at high framerates
@github-actions github-actions bot added size: large A large pull request with more than 100 changes. pr: documentation PR modifies documentation or README files. labels Nov 27, 2024
@KutikiPlayz KutikiPlayz marked this pull request as draft November 27, 2024 03:02
based on minecraft's tickDelta variable they use for smoother rendering at high framerates
This reverts commit 19b55dc.
@KutikiPlayz KutikiPlayz force-pushed the frame-song-position-for-note-rendering branch from b62414f to ba17caa Compare November 27, 2024 03:05
@github-actions github-actions bot added size: medium A medium pull request with 100 or fewer changes. and removed size: large A large pull request with more than 100 changes. labels Nov 27, 2024
@KutikiPlayz KutikiPlayz marked this pull request as ready for review November 27, 2024 03:06
@xenkap
Copy link

xenkap commented Nov 27, 2024

Nice
I forgot to say this back in the review, but there also seems to be a problem with this PR interacting with Linux (at least my Ubuntu installation) and audio device latency. Could be outside the scope of this PR though?

FNF-2024-11-27_14.39.13.mp4

Linux seems to correctly retrieve the time of playing audio, even accounting for audio device latency. This means that even with pretty much 0 audio/visual offset and using Bluetooth headphones, the game will continue to exactly sync with what you can see. However, this means that the audio stays at position 0 for quite a while until the song becomes audible.
The PR recognizes this as "Hey, songPosition hasn't updated yet! Let's increment songPositionDelta". Then it lags waaay back when the audio finally starts being audible and songPosition actually updates-- it's bad enough for me that it reaches up to 300 on songPositionDelta.

Not really sure how you'd fix this anyway, but an idea I had was to wait until songPosition updates after any pauses before allowing songPositionDelta to increment-- and it's probably the best way to go about it? (Asides finding a way to disable this behavior entirely, maybe) I tried implementing it on my own using a boolean, but it was pretty janky...

@AbnormalPoof AbnormalPoof added the topic: polish Involves minor polish to the UI or gameplay. label Jan 22, 2025
@Hundrec Hundrec removed the pr: documentation PR modifies documentation or README files. label Jan 23, 2025
@Lasercar
Copy link
Contributor

This PR closes this issue: #3127

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: haxe PR modifies game code. size: medium A medium pull request with 100 or fewer changes. status: needs revision Cannot be approved because it is awaiting some work by the contributor. topic: polish Involves minor polish to the UI or gameplay. type: optimization Involves a performance issue or a bug which causes lag.
Projects
None yet
Development

Successfully merging this pull request may close these issues.