@@ -5,7 +5,6 @@ import androidx.annotation.IdRes
55import com.edlplan.framework.easing.Easing
66import com.osudroid.ui.v1.SettingsFragment
77import com.osudroid.utils.mainThread
8- import com.osudroid.utils.standardDeviation
98import com.osudroid.utils.updateThread
109import com.reco1l.andengine.Anchor
1110import com.reco1l.andengine.UIEngine
@@ -38,8 +37,11 @@ import com.reco1l.framework.math.Vec4
3837import com.reco1l.osu.ui.PromptDialog
3938import com.osudroid.beatmaps.DroidHitWindow
4039import com.osudroid.math.Interpolation
40+ import com.osudroid.utils.median
41+ import com.osudroid.utils.standardDeviation
4142import com.rian.andengine.modifier.ModifierType
4243import kotlin.math.abs
44+ import kotlin.math.exp
4345import kotlin.math.roundToInt
4446import org.andengine.input.touch.TouchEvent
4547import ru.nsu.ccfit.zuev.audio.Status
@@ -547,7 +549,7 @@ object CalibrationScene : UIScene() {
547549 tapOffsets.removeAt(0 )
548550 }
549551
550- pendingOffset = tapOffsets.standardDeviation().roundToInt().coerceIn( OFFSET_MIN , OFFSET_MAX )
552+ pendingOffset = computePendingOffset( )
551553
552554 val judgement = when {
553555 absErr < judgementHitWindow.greatWindow -> Judgement .PERFECT
@@ -784,4 +786,21 @@ object CalibrationScene : UIScene() {
784786 Config .setOffset(pendingOffset.toFloat())
785787 Config .setInt(" offset" , pendingOffset)
786788 }
789+
790+ private fun computePendingOffset (): Int {
791+ if (tapOffsets.isEmpty()) {
792+ return 0
793+ }
794+
795+ val unstableRate = tapOffsets.standardDeviation()
796+ var offset = tapOffsets.median()
797+
798+ if (unstableRate >= 90 ) {
799+ // A demonstrative graph of this algorithm is embedded in https://github.com/ppy/osu/discussions/30521.
800+ // This prevents high unstable rate from suggesting potentially invalid offsets.
801+ offset * = exp(- 0.0116 * (unstableRate - 90 ))
802+ }
803+
804+ return offset.roundToInt().coerceIn(OFFSET_MIN , OFFSET_MAX )
805+ }
787806}
0 commit comments