Difficulty Calculation - nbayt/BeatSaberSongLoader GitHub Wiki
Difficulty Calculation
A quick overview on how difficulty is calculated for songs.
-
First each note in the song is assigned a difficulty score according to the following criteria.
- Firstly the distance is considered between the current note and the previous same color note.
- Next we compute the "Positional Difficulty" current note.
- Basically if a blue note is on the far left, it is harder to hit in general, vice versa for red.
- Another bonus if the note is high up.
- Next is cut awkwardness, which is defined as cutting in the same direction.
- This only applies if the note is "behind" the previous note cut direction wise.
- Plan is to support based on cut angles as well.
- Next up is readability, we add up each note in the stack's score together and return that.
- We only consider notes less than or equal to one beat away.
- Notes that are fully masked are given a higher score, partial masking is less of course.
- Finally the temporal distance from the last note of the same color.
- Currently
Math.Pow(1.0f + diff_score, 1f + (0.375 / (dist_time*_secondsPerBeat)) * 0.87f)
- Time was changed to be based on a 160 BPM song, so a 4/4 on a 80bpm song is the same as 2/1\4 on 160bpm.
- Currently
- Another scalar is applied if the note is a circle note.
- If prior same color note is also circle, then severe difficulty reduction.
-
Next up! Average difficulty
- Self explanatory, sum up red and divide by total, sum up blue divide by total.
- Then
var total_avg = (red_avg + blue_avg) / 1.96f;
no strong reason why I divide by 1.96. - We multiply this by 50.0 later, to bring in line with the next step. Keep tweaking.
-
Almost done, now we compute the strain/endurance of the song.
- Break the song up into
N
beat chunks, whereN
will be: 1,2,4,8.- We find the strain value for each chunk (sum of diffs that fall into that beat section).
- Use these chunks to find the avg and std deviation of the strain values.
- Currently strain is the sum of
score += (float)Math.Pow(strain / peak_strain, 2f) * (1f + bonus);
for each chunk.- Bonus is a bonus mult that goes up when the curr strain is near or above avg.
- If it falls below avg then bonus gets reduced (think long pauses and the such, you have time to recover).
- Bonus is a bonus mult that goes up when the curr strain is near or above avg.
- Then it is modified by
score *= 1f + ((float)i / (float)strains.Count) * 0.3f;
- I did this to make the end of the song worth more in strain, because you have lots of energy (I hope) at the start!
- Want to add a further bonus purely based on length.
- I then added
strain_score *= (peak_strain / 3f) * (strain_avg / 2f) * Mathf.Clamp(10f/strain_dev,0.6f,2.0f);
to handle easy songs and weight them less. - I then sqrt it
strain_score = (float)Math.Sqrt(strain_score);
because of rapid score growth in harder songs.
- We return these values for each
N
chunks and do a weighted avg based onN
.
- Break the song up into
-
Finally, add the avg and endurance scores together and preform a length bonus on it.
final_scaled_diff = final_scaled_diff * (1f + (_length*_secondsPerBeat / 90f) * 0.20f);
- I divide by 500 at the end to bring the numbers down.
This is obviously not perfect and needs a lot more work but I think it does a decent job honestly.