diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs
index 3ff5b87fb6..b715dfc37a 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs
@@ -10,32 +10,8 @@ using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data;
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
{
- public class ColourEvaluator
+ public static class ColourEvaluator
{
- ///
- /// Evaluate the difficulty of the first note of a .
- ///
- public static double EvaluateDifficultyOf(MonoStreak monoStreak)
- {
- return DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5;
- }
-
- ///
- /// Evaluate the difficulty of the first note of a .
- ///
- public static double EvaluateDifficultyOf(AlternatingMonoPattern alternatingMonoPattern)
- {
- return DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * EvaluateDifficultyOf(alternatingMonoPattern.Parent);
- }
-
- ///
- /// Evaluate the difficulty of the first note of a .
- ///
- public static double EvaluateDifficultyOf(RepeatingHitPatterns repeatingHitPattern)
- {
- return 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E));
- }
-
///
/// Calculates a consistency penalty based on the number of consecutive consistent intervals,
/// considering the delta time between each colour sequence.
@@ -58,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
var previousHitObject = (TaikoDifficultyHitObject)current.Previous(1);
- double currentRatio = current.Rhythm.Ratio;
- double previousRatio = previousHitObject.Rhythm.Ratio;
+ double currentRatio = current.RhythmData.Ratio;
+ double previousRatio = previousHitObject.RhythmData.Ratio;
// A consistent interval is defined as the percentage difference between the two rhythmic ratios with the margin of error.
if (Math.Abs(1 - currentRatio / previousRatio) <= threshold)
@@ -85,22 +61,31 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
{
var taikoObject = (TaikoDifficultyHitObject)hitObject;
- TaikoDifficultyHitObjectColour colour = taikoObject.Colour;
+ TaikoColourData colourData = taikoObject.ColourData;
double difficulty = 0.0d;
- if (colour.MonoStreak?.FirstHitObject == hitObject) // Difficulty for MonoStreak
- difficulty += EvaluateDifficultyOf(colour.MonoStreak);
+ if (colourData.MonoStreak?.FirstHitObject == hitObject) // Difficulty for MonoStreak
+ difficulty += evaluateMonoStreakDifficulty(colourData.MonoStreak);
- if (colour.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern
- difficulty += EvaluateDifficultyOf(colour.AlternatingMonoPattern);
+ if (colourData.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern
+ difficulty += evaluateAlternatingMonoPatternDifficulty(colourData.AlternatingMonoPattern);
- if (colour.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern
- difficulty += EvaluateDifficultyOf(colour.RepeatingHitPattern);
+ if (colourData.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern
+ difficulty += evaluateRepeatingHitPatternsDifficulty(colourData.RepeatingHitPattern);
double consistencyPenalty = consistentRatioPenalty(taikoObject);
difficulty *= consistencyPenalty;
return difficulty;
}
+
+ private static double evaluateMonoStreakDifficulty(MonoStreak monoStreak) =>
+ DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * evaluateAlternatingMonoPatternDifficulty(monoStreak.Parent) * 0.5;
+
+ private static double evaluateAlternatingMonoPatternDifficulty(AlternatingMonoPattern alternatingMonoPattern) =>
+ DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * evaluateRepeatingHitPatternsDifficulty(alternatingMonoPattern.Parent);
+
+ private static double evaluateRepeatingHitPatternsDifficulty(RepeatingHitPatterns repeatingHitPattern) =>
+ 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E));
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ReadingEvaluator.cs
index 2a08f65c7b..5871979613 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ReadingEvaluator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ReadingEvaluator.cs
@@ -47,8 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
// High density is penalised at high velocity as it is generally considered easier to read. See https://www.desmos.com/calculator/u63f3ntdsi
double densityPenalty = DifficultyCalculationUtils.Logistic(objectDensity, 0.925, 15);
- double highVelocityDifficulty = (1.0 - 0.33 * densityPenalty) * DifficultyCalculationUtils.Logistic
- (effectiveBPM, highVelocity.Center + 8 * densityPenalty, (1.0 + 0.5 * densityPenalty) / (highVelocity.Range / 10));
+ double highVelocityDifficulty = (1.0 - 0.33 * densityPenalty)
+ * DifficultyCalculationUtils.Logistic(effectiveBPM, highVelocity.Center + 8 * densityPenalty, (1.0 + 0.5 * densityPenalty) / (highVelocity.Range / 10));
return midVelocityDifficulty + highVelocityDifficulty;
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs
index 22321a8f6e..3b3aea07f3 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -18,39 +18,39 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
///
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject, double hitWindow)
{
- TaikoDifficultyHitObjectRhythm rhythm = ((TaikoDifficultyHitObject)hitObject).Rhythm;
+ TaikoRhythmData rhythmData = ((TaikoDifficultyHitObject)hitObject).RhythmData;
double difficulty = 0.0d;
double sameRhythm = 0;
double samePattern = 0;
double intervalPenalty = 0;
- if (rhythm.SameRhythmHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmHitObjects
+ if (rhythmData.SameRhythmGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmGroupedHitObjects
{
- sameRhythm += 10.0 * evaluateDifficultyOf(rhythm.SameRhythmHitObjects, hitWindow);
- intervalPenalty = repeatedIntervalPenalty(rhythm.SameRhythmHitObjects, hitWindow);
+ sameRhythm += 10.0 * evaluateDifficultyOf(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
+ intervalPenalty = repeatedIntervalPenalty(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
}
- if (rhythm.SamePatterns?.FirstHitObject == hitObject) // Difficulty for SamePatterns
- samePattern += 1.15 * ratioDifficulty(rhythm.SamePatterns.IntervalRatio);
+ if (rhythmData.SamePatternsGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SamePatternsGroupedHitObjects
+ samePattern += 1.15 * ratioDifficulty(rhythmData.SamePatternsGroupedHitObjects.IntervalRatio);
difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty;
return difficulty;
}
- private static double evaluateDifficultyOf(SameRhythmHitObjects sameRhythmHitObjects, double hitWindow)
+ private static double evaluateDifficultyOf(SameRhythmHitObjectGrouping sameRhythmGroupedHitObjects, double hitWindow)
{
- double intervalDifficulty = ratioDifficulty(sameRhythmHitObjects.HitObjectIntervalRatio);
- double? previousInterval = sameRhythmHitObjects.Previous?.HitObjectInterval;
+ double intervalDifficulty = ratioDifficulty(sameRhythmGroupedHitObjects.HitObjectIntervalRatio);
+ double? previousInterval = sameRhythmGroupedHitObjects.Previous?.HitObjectInterval;
- intervalDifficulty *= repeatedIntervalPenalty(sameRhythmHitObjects, hitWindow);
+ intervalDifficulty *= repeatedIntervalPenalty(sameRhythmGroupedHitObjects, hitWindow);
// If a previous interval exists and there are multiple hit objects in the sequence:
- if (previousInterval != null && sameRhythmHitObjects.Children.Count > 1)
+ if (previousInterval != null && sameRhythmGroupedHitObjects.HitObjects.Count > 1)
{
- double expectedDurationFromPrevious = (double)previousInterval * sameRhythmHitObjects.Children.Count;
- double durationDifference = sameRhythmHitObjects.Duration - expectedDurationFromPrevious;
+ double expectedDurationFromPrevious = (double)previousInterval * sameRhythmGroupedHitObjects.HitObjects.Count;
+ double durationDifference = sameRhythmGroupedHitObjects.Duration - expectedDurationFromPrevious;
if (durationDifference > 0)
{
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
// Penalise patterns that can be hit within a single hit window.
intervalDifficulty *= DifficultyCalculationUtils.Logistic(
- sameRhythmHitObjects.Duration / hitWindow,
+ sameRhythmGroupedHitObjects.Duration / hitWindow,
midpointOffset: 0.6,
multiplier: 1,
maxValue: 1);
@@ -75,20 +75,20 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
///
/// Determines if the changes in hit object intervals is consistent based on a given threshold.
///
- private static double repeatedIntervalPenalty(SameRhythmHitObjects sameRhythmHitObjects, double hitWindow, double threshold = 0.1)
+ private static double repeatedIntervalPenalty(SameRhythmHitObjectGrouping sameRhythmGroupedHitObjects, double hitWindow, double threshold = 0.1)
{
- double longIntervalPenalty = sameInterval(sameRhythmHitObjects, 3);
+ double longIntervalPenalty = sameInterval(sameRhythmGroupedHitObjects, 3);
- double shortIntervalPenalty = sameRhythmHitObjects.Children.Count < 6
- ? sameInterval(sameRhythmHitObjects, 4)
+ double shortIntervalPenalty = sameRhythmGroupedHitObjects.HitObjects.Count < 6
+ ? sameInterval(sameRhythmGroupedHitObjects, 4)
: 1.0; // Returns a non-penalty if there are 6 or more notes within an interval.
// The duration penalty is based on hit object duration relative to hitWindow.
- double durationPenalty = Math.Max(1 - sameRhythmHitObjects.Duration * 2 / hitWindow, 0.5);
+ double durationPenalty = Math.Max(1 - sameRhythmGroupedHitObjects.Duration * 2 / hitWindow, 0.5);
return Math.Min(longIntervalPenalty, shortIntervalPenalty) * durationPenalty;
- double sameInterval(SameRhythmHitObjects startObject, int intervalCount)
+ double sameInterval(SameRhythmHitObjectGrouping startObject, int intervalCount)
{
List intervals = new List();
var currentObject = startObject;
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs
index b39ad953a4..32ed8ec189 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs
@@ -8,43 +8,8 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
{
- public class StaminaEvaluator
+ public static class StaminaEvaluator
{
- ///
- /// Applies a speed bonus dependent on the time since the last hit performed using this finger.
- ///
- /// The interval between the current and previous note hit using the same finger.
- private static double speedBonus(double interval)
- {
- // Interval is capped at a very small value to prevent infinite values.
- interval = Math.Max(interval, 1);
-
- return 20 / interval;
- }
-
- ///
- /// Determines the number of fingers available to hit the current .
- /// Any mono notes that is more than 300ms apart from a colour change will be considered to have more than 2
- /// fingers available, since players can hit the same key with multiple fingers.
- ///
- private static int availableFingersFor(TaikoDifficultyHitObject hitObject)
- {
- DifficultyHitObject? previousColourChange = hitObject.Colour.PreviousColourChange;
- DifficultyHitObject? nextColourChange = hitObject.Colour.NextColourChange;
-
- if (previousColourChange != null && hitObject.StartTime - previousColourChange.StartTime < 300)
- {
- return 2;
- }
-
- if (nextColourChange != null && nextColourChange.StartTime - hitObject.StartTime < 300)
- {
- return 2;
- }
-
- return 8;
- }
-
///
/// Evaluates the minimum mechanical stamina required to play the current object. This is calculated using the
/// maximum possible interval between two hits using the same key, by alternating available fingers for each colour.
@@ -70,5 +35,40 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
return objectStrain;
}
+
+ ///
+ /// Applies a speed bonus dependent on the time since the last hit performed using this finger.
+ ///
+ /// The interval between the current and previous note hit using the same finger.
+ private static double speedBonus(double interval)
+ {
+ // Interval is capped at a very small value to prevent infinite values.
+ interval = Math.Max(interval, 1);
+
+ return 20 / interval;
+ }
+
+ ///
+ /// Determines the number of fingers available to hit the current .
+ /// Any mono notes that is more than 300ms apart from a colour change will be considered to have more than 2
+ /// fingers available, since players can hit the same key with multiple fingers.
+ ///
+ private static int availableFingersFor(TaikoDifficultyHitObject hitObject)
+ {
+ DifficultyHitObject? previousColourChange = hitObject.ColourData.PreviousColourChange;
+ DifficultyHitObject? nextColourChange = hitObject.ColourData.NextColourChange;
+
+ if (previousColourChange != null && hitObject.StartTime - previousColourChange.StartTime < 300)
+ {
+ return 2;
+ }
+
+ if (nextColourChange != null && nextColourChange.StartTime - hitObject.StartTime < 300)
+ {
+ return 2;
+ }
+
+ return 8;
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoDifficultyHitObjectColour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourData.cs
similarity index 96%
rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoDifficultyHitObjectColour.cs
rename to osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourData.cs
index abf6fb3672..81201b6584 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoDifficultyHitObjectColour.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourData.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour
///
/// Stores colour compression information for a .
///
- public class TaikoDifficultyHitObjectColour
+ public class TaikoColourData
{
///
/// The that encodes this note.
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourDifficultyPreprocessor.cs
index 18a299ae92..3c6ef7c53c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourDifficultyPreprocessor.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/TaikoColourDifficultyPreprocessor.cs
@@ -14,8 +14,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour
public static class TaikoColourDifficultyPreprocessor
{
///
- /// Processes and encodes a list of s into a list of s,
- /// assigning the appropriate s to each .
+ /// Processes and encodes a list of s into a list of s,
+ /// assigning the appropriate s to each .
///
public static void ProcessAndAssign(List hitObjects)
{
@@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour
foreach (var hitObject in monoStreak.HitObjects)
{
- hitObject.Colour.RepeatingHitPattern = repeatingHitPattern;
- hitObject.Colour.AlternatingMonoPattern = monoPattern;
- hitObject.Colour.MonoStreak = monoStreak;
+ hitObject.ColourData.RepeatingHitPattern = repeatingHitPattern;
+ hitObject.ColourData.AlternatingMonoPattern = monoPattern;
+ hitObject.ColourData.MonoStreak = monoStreak;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Reading/EffectiveBPM.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Reading/EffectiveBPM.cs
deleted file mode 100644
index 17e05d5fbf..0000000000
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Reading/EffectiveBPM.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-
-namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Reading
-{
- public class EffectiveBPMPreprocessor
- {
- private readonly IList noteObjects;
- private readonly double globalSliderVelocity;
-
- public EffectiveBPMPreprocessor(IBeatmap beatmap, List noteObjects)
- {
- this.noteObjects = noteObjects;
- globalSliderVelocity = beatmap.Difficulty.SliderMultiplier;
- }
-
- ///
- /// Calculates and sets the effective BPM and slider velocity for each note object, considering clock rate and scroll speed.
- ///
- public void ProcessEffectiveBPM(ControlPointInfo controlPointInfo, double clockRate)
- {
- foreach (var currentNoteObject in noteObjects)
- {
- double startTime = currentNoteObject.StartTime * clockRate;
-
- // Retrieve the timing point at the note's start time
- TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(startTime);
-
- // Calculate the slider velocity at the note's start time.
- double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, startTime, clockRate);
- currentNoteObject.CurrentSliderVelocity = currentSliderVelocity;
-
- currentNoteObject.EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity;
- }
- }
-
- ///
- /// Calculates the slider velocity based on control point info and clock rate.
- ///
- private double calculateSliderVelocity(ControlPointInfo controlPointInfo, double startTime, double clockRate)
- {
- var activeEffectControlPoint = controlPointInfo.EffectPointAt(startTime);
- return globalSliderVelocity * (activeEffectControlPoint.ScrollSpeed) * clockRate;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatterns.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatterns.cs
deleted file mode 100644
index 50839c4561..0000000000
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatterns.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using System.Linq;
-
-namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
-{
- ///
- /// Represents grouped by their 's interval.
- ///
- public class SamePatterns : SameRhythm
- {
- public SamePatterns? Previous { get; private set; }
-
- ///
- /// The between children within this group.
- /// If there is only one child, this will have the value of the first child's .
- ///
- public double ChildrenInterval => Children.Count > 1 ? Children[1].Interval : Children[0].Interval;
-
- ///
- /// The ratio of between this and the previous . In the
- /// case where there is no previous , this will have a value of 1.
- ///
- public double IntervalRatio => ChildrenInterval / Previous?.ChildrenInterval ?? 1.0d;
-
- public TaikoDifficultyHitObject FirstHitObject => Children[0].FirstHitObject;
-
- public IEnumerable AllHitObjects => Children.SelectMany(child => child.Children);
-
- private SamePatterns(SamePatterns? previous, List data, ref int i)
- : base(data, ref i, 5)
- {
- Previous = previous;
-
- foreach (TaikoDifficultyHitObject hitObject in AllHitObjects)
- {
- hitObject.Rhythm.SamePatterns = this;
- }
- }
-
- public static void GroupPatterns(List data)
- {
- List samePatterns = new List();
-
- // Index does not need to be incremented, as it is handled within the SameRhythm constructor.
- for (int i = 0; i < data.Count;)
- {
- SamePatterns? previous = samePatterns.Count > 0 ? samePatterns[^1] : null;
- samePatterns.Add(new SamePatterns(previous, data, ref i));
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs
new file mode 100644
index 0000000000..938cb4670f
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
+{
+ ///
+ /// Represents grouped by their 's interval.
+ ///
+ public class SamePatternsGroupedHitObjects
+ {
+ public IReadOnlyList Groups { get; }
+
+ public SamePatternsGroupedHitObjects? Previous { get; }
+
+ ///
+ /// The between groups .
+ /// If there is only one group, this will have the value of the first group's .
+ ///
+ public double GroupInterval => Groups.Count > 1 ? Groups[1].Interval : Groups[0].Interval;
+
+ ///
+ /// The ratio of between this and the previous . In the
+ /// case where there is no previous , this will have a value of 1.
+ ///
+ public double IntervalRatio => GroupInterval / Previous?.GroupInterval ?? 1.0d;
+
+ public TaikoDifficultyHitObject FirstHitObject => Groups[0].FirstHitObject;
+
+ public IEnumerable AllHitObjects => Groups.SelectMany(hitObject => hitObject.HitObjects);
+
+ public SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List groups)
+ {
+ Previous = previous;
+ Groups = groups;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythm.cs
deleted file mode 100644
index b1ca22595b..0000000000
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythm.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-
-namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
-{
- ///
- /// A base class for grouping s by their interval. In edges where an interval change
- /// occurs, the is added to the group with the smaller interval.
- ///
- public abstract class SameRhythm
- where ChildType : IHasInterval
- {
- public IReadOnlyList Children { get; private set; }
-
- ///
- /// Determines if the intervals between two child objects are within a specified margin of error,
- /// indicating that the intervals are effectively "flat" or consistent.
- ///
- private bool isFlat(ChildType current, ChildType previous, double marginOfError)
- {
- return Math.Abs(current.Interval - previous.Interval) <= marginOfError;
- }
-
- ///
- /// Create a new from a list of s, and add
- /// them to the list until the end of the group.
- ///
- /// The list of s.
- ///
- /// Index in to start adding children. This will be modified and should be passed into
- /// the next 's constructor.
- ///
- ///
- /// The margin of error for the interval, within of which no interval change is considered to have occured.
- ///
- protected SameRhythm(List data, ref int i, double marginOfError)
- {
- List children = new List();
- Children = children;
- children.Add(data[i]);
- i++;
-
- for (; i < data.Count - 1; i++)
- {
- // An interval change occured, add the current data if the next interval is larger.
- if (!isFlat(data[i], data[i + 1], marginOfError))
- {
- if (data[i + 1].Interval > data[i].Interval + marginOfError)
- {
- children.Add(data[i]);
- i++;
- }
-
- return;
- }
-
- // No interval change occured
- children.Add(data[i]);
- }
-
- // Check if the last two objects in the data form a "flat" rhythm pattern within the specified margin of error.
- // If true, add the current object to the group and increment the index to process the next object.
- if (data.Count > 2 && isFlat(data[^1], data[^2], marginOfError))
- {
- children.Add(data[i]);
- i++;
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjectGrouping.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjectGrouping.cs
new file mode 100644
index 0000000000..9caa9b9958
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjectGrouping.cs
@@ -0,0 +1,63 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Taiko.Difficulty.Utils;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
+{
+ ///
+ /// Represents a group of s with no rhythm variation.
+ ///
+ public class SameRhythmHitObjectGrouping : IHasInterval
+ {
+ public readonly List HitObjects;
+
+ public TaikoDifficultyHitObject FirstHitObject => HitObjects[0];
+
+ public readonly SameRhythmHitObjectGrouping? Previous;
+
+ ///
+ /// of the first hit object.
+ ///
+ public double StartTime => HitObjects[0].StartTime;
+
+ ///
+ /// The interval between the first and final hit object within this group.
+ ///
+ public double Duration => HitObjects[^1].StartTime - HitObjects[0].StartTime;
+
+ ///
+ /// The interval in ms of each hit object in this . This is only defined if there is
+ /// more than two hit objects in this .
+ ///
+ public readonly double? HitObjectInterval;
+
+ ///
+ /// The ratio of between this and the previous . In the
+ /// case where one or both of the is undefined, this will have a value of 1.
+ ///
+ public readonly double HitObjectIntervalRatio;
+
+ ///
+ public double Interval { get; }
+
+ public SameRhythmHitObjectGrouping(SameRhythmHitObjectGrouping? previous, List hitObjects)
+ {
+ Previous = previous;
+ HitObjects = hitObjects;
+
+ // Calculate the average interval between hitobjects, or null if there are fewer than two
+ HitObjectInterval = HitObjects.Count < 2 ? null : Duration / (HitObjects.Count - 1);
+
+ // Calculate the ratio between this group's interval and the previous group's interval
+ HitObjectIntervalRatio = Previous?.HitObjectInterval != null && HitObjectInterval != null
+ ? HitObjectInterval.Value / Previous.HitObjectInterval.Value
+ : 1;
+
+ // Calculate the interval from the previous group's start time
+ Interval = Previous != null ? StartTime - Previous.StartTime : double.PositiveInfinity;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjects.cs
deleted file mode 100644
index 0ccc6da026..0000000000
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjects.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Rulesets.Difficulty.Preprocessing;
-
-namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
-{
- ///
- /// Represents a group of s with no rhythm variation.
- ///
- public class SameRhythmHitObjects : SameRhythm, IHasInterval
- {
- public TaikoDifficultyHitObject FirstHitObject => Children[0];
-
- public SameRhythmHitObjects? Previous;
-
- ///
- /// of the first hit object.
- ///
- public double StartTime => Children[0].StartTime;
-
- ///
- /// The interval between the first and final hit object within this group.
- ///
- public double Duration => Children[^1].StartTime - Children[0].StartTime;
-
- ///
- /// The interval in ms of each hit object in this . This is only defined if there is
- /// more than two hit objects in this .
- ///
- public double? HitObjectInterval;
-
- ///
- /// The ratio of between this and the previous . In the
- /// case where one or both of the is undefined, this will have a value of 1.
- ///
- public double HitObjectIntervalRatio = 1;
-
- ///
- /// The interval between the of this and the previous .
- ///
- public double Interval { get; private set; } = double.PositiveInfinity;
-
- public SameRhythmHitObjects(SameRhythmHitObjects? previous, List data, ref int i)
- : base(data, ref i, 5)
- {
- Previous = previous;
-
- foreach (var hitObject in Children)
- {
- hitObject.Rhythm.SameRhythmHitObjects = this;
-
- // Pass the HitObjectInterval to each child.
- hitObject.HitObjectInterval = HitObjectInterval;
- }
-
- calculateIntervals();
- }
-
- public static List GroupHitObjects(List data)
- {
- List flatPatterns = new List();
-
- // Index does not need to be incremented, as it is handled within SameRhythm's constructor.
- for (int i = 0; i < data.Count;)
- {
- SameRhythmHitObjects? previous = flatPatterns.Count > 0 ? flatPatterns[^1] : null;
- flatPatterns.Add(new SameRhythmHitObjects(previous, data, ref i));
- }
-
- return flatPatterns;
- }
-
- private void calculateIntervals()
- {
- // Calculate the average interval between hitobjects, or null if there are fewer than two.
- HitObjectInterval = Children.Count < 2 ? null : (Children[^1].StartTime - Children[0].StartTime) / (Children.Count - 1);
-
- // If both the current and previous intervals are available, calculate the ratio.
- if (Previous?.HitObjectInterval != null && HitObjectInterval != null)
- {
- HitObjectIntervalRatio = HitObjectInterval.Value / Previous.HitObjectInterval.Value;
- }
-
- if (Previous == null)
- {
- return;
- }
-
- Interval = StartTime - Previous.StartTime;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs
deleted file mode 100644
index beb7bfe5f6..0000000000
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Linq;
-using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
-
-namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
-{
- ///
- /// Stores rhythm data for a .
- ///
- public class TaikoDifficultyHitObjectRhythm
- {
- ///
- /// The group of hit objects with consistent rhythm that this object belongs to.
- ///
- public SameRhythmHitObjects? SameRhythmHitObjects;
-
- ///
- /// The larger pattern of rhythm groups that this object is part of.
- ///
- public SamePatterns? SamePatterns;
-
- ///
- /// The ratio of current
- /// to previous for the rhythm change.
- /// A above 1 indicates a slow-down; a below 1 indicates a speed-up.
- ///
- public readonly double Ratio;
-
- ///
- /// List of most common rhythm changes in taiko maps. Based on how each object's interval compares to the previous object.
- ///
- ///
- /// The general guidelines for the values are:
- ///
- /// - rhythm changes with ratio closer to 1 (that are not 1) are harder to play,
- /// - speeding up is generally harder than slowing down (with exceptions of rhythm changes requiring a hand switch).
- ///
- ///
- private static readonly TaikoDifficultyHitObjectRhythm[] common_rhythms =
- {
- new TaikoDifficultyHitObjectRhythm(1, 1),
- new TaikoDifficultyHitObjectRhythm(2, 1),
- new TaikoDifficultyHitObjectRhythm(1, 2),
- new TaikoDifficultyHitObjectRhythm(3, 1),
- new TaikoDifficultyHitObjectRhythm(1, 3),
- new TaikoDifficultyHitObjectRhythm(3, 2),
- new TaikoDifficultyHitObjectRhythm(2, 3),
- new TaikoDifficultyHitObjectRhythm(5, 4),
- new TaikoDifficultyHitObjectRhythm(4, 5)
- };
-
- ///
- /// Initialises a new instance of s,
- /// calculating the closest rhythm change and its associated difficulty for the current hit object.
- ///
- /// The current being processed.
- public TaikoDifficultyHitObjectRhythm(TaikoDifficultyHitObject current)
- {
- var previous = current.Previous(0);
-
- if (previous == null)
- {
- Ratio = 1;
- return;
- }
-
- TaikoDifficultyHitObjectRhythm closestRhythm = getClosestRhythm(current.DeltaTime, previous.DeltaTime);
- Ratio = closestRhythm.Ratio;
- }
-
- ///
- /// Creates an object representing a rhythm change.
- ///
- /// The numerator for .
- /// The denominator for
- private TaikoDifficultyHitObjectRhythm(int numerator, int denominator)
- {
- Ratio = numerator / (double)denominator;
- }
-
- ///
- /// Determines the closest rhythm change from that matches the timing ratio
- /// between the current and previous intervals.
- ///
- /// The time difference between the current hit object and the previous one.
- /// The time difference between the previous hit object and the one before it.
- /// The closest matching rhythm from .
- private TaikoDifficultyHitObjectRhythm getClosestRhythm(double currentDeltaTime, double previousDeltaTime)
- {
- double ratio = currentDeltaTime / previousDeltaTime;
- return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First();
- }
- }
-}
-
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs
new file mode 100644
index 0000000000..6c4a332624
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs
@@ -0,0 +1,79 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
+{
+ ///
+ /// Stores rhythm data for a .
+ ///
+ public class TaikoRhythmData
+ {
+ ///
+ /// The group of hit objects with consistent rhythm that this object belongs to.
+ ///
+ public SameRhythmHitObjectGrouping? SameRhythmGroupedHitObjects;
+
+ ///
+ /// The larger pattern of rhythm groups that this object is part of.
+ ///
+ public SamePatternsGroupedHitObjects? SamePatternsGroupedHitObjects;
+
+ ///
+ /// The ratio of current
+ /// to previous for the rhythm change.
+ /// A above 1 indicates a slow-down; a below 1 indicates a speed-up.
+ ///
+ ///
+ /// This is snapped to the closest matching .
+ ///
+ public readonly double Ratio;
+
+ ///
+ /// Initialises a new instance of s,
+ /// calculating the closest rhythm change and its associated difficulty for the current hit object.
+ ///
+ /// The current being processed.
+ public TaikoRhythmData(TaikoDifficultyHitObject current)
+ {
+ var previous = current.Previous(0);
+
+ if (previous == null)
+ {
+ Ratio = 1;
+ return;
+ }
+
+ double actualRatio = current.DeltaTime / previous.DeltaTime;
+ double closestRatio = common_ratios.OrderBy(r => Math.Abs(r - actualRatio)).First();
+
+ Ratio = closestRatio;
+ }
+
+ ///
+ /// List of most common rhythm changes in taiko maps. Based on how each object's interval compares to the previous object.
+ ///
+ ///
+ /// The general guidelines for the values are:
+ ///
+ /// - rhythm changes with ratio closer to 1 (that are not 1) are harder to play,
+ /// - speeding up is generally harder than slowing down (with exceptions of rhythm changes requiring a hand switch).
+ ///
+ ///
+ private static readonly double[] common_ratios = new[]
+ {
+ 1.0 / 1,
+ 2.0 / 1,
+ 1.0 / 2,
+ 3.0 / 1,
+ 1.0 / 3,
+ 3.0 / 2,
+ 2.0 / 3,
+ 5.0 / 4,
+ 4.0 / 5
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs
new file mode 100644
index 0000000000..5bc0fdbc03
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
+using osu.Game.Rulesets.Taiko.Difficulty.Utils;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
+{
+ public static class TaikoRhythmDifficultyPreprocessor
+ {
+ public static void ProcessAndAssign(List hitObjects)
+ {
+ var rhythmGroups = createSameRhythmGroupedHitObjects(hitObjects);
+
+ foreach (var rhythmGroup in rhythmGroups)
+ {
+ foreach (var hitObject in rhythmGroup.HitObjects)
+ hitObject.RhythmData.SameRhythmGroupedHitObjects = rhythmGroup;
+ }
+
+ var patternGroups = createSamePatternGroupedHitObjects(rhythmGroups);
+
+ foreach (var patternGroup in patternGroups)
+ {
+ foreach (var hitObject in patternGroup.AllHitObjects)
+ hitObject.RhythmData.SamePatternsGroupedHitObjects = patternGroup;
+ }
+ }
+
+ private static List createSameRhythmGroupedHitObjects(List hitObjects)
+ {
+ var rhythmGroups = new List();
+
+ foreach (var grouped in IntervalGroupingUtils.GroupByInterval(hitObjects))
+ rhythmGroups.Add(new SameRhythmHitObjectGrouping(rhythmGroups.LastOrDefault(), grouped));
+
+ return rhythmGroups;
+ }
+
+ private static List createSamePatternGroupedHitObjects(List rhythmGroups)
+ {
+ var patternGroups = new List();
+
+ foreach (var grouped in IntervalGroupingUtils.GroupByInterval(rhythmGroups))
+ patternGroups.Add(new SamePatternsGroupedHitObjects(patternGroups.LastOrDefault(), grouped));
+
+ return patternGroups;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
index dfcd08ed94..f407e13ff1 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
@@ -3,11 +3,14 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
+using osu.Game.Rulesets.Taiko.Difficulty.Utils;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
{
@@ -37,80 +40,89 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
public readonly int NoteIndex;
///
- /// The rhythm required to hit this hit object.
+ /// Rhythm data used by .
+ /// This is populated via .
///
- public readonly TaikoDifficultyHitObjectRhythm Rhythm;
+ public readonly TaikoRhythmData RhythmData;
///
- /// The interval between this hit object and the surrounding hit objects in its rhythm group.
+ /// Colour data used by and .
+ /// This is populated via .
///
- public double? HitObjectInterval { get; set; }
-
- ///
- /// Colour data for this hit object. This is used by colour evaluator to calculate colour difficulty, but can be used
- /// by other skills in the future.
- ///
- public readonly TaikoDifficultyHitObjectColour Colour;
+ public readonly TaikoColourData ColourData;
///
/// The adjusted BPM of this hit object, based on its slider velocity and scroll speed.
///
public double EffectiveBPM;
- ///
- /// The current slider velocity of this hit object.
- ///
- public double CurrentSliderVelocity;
-
- public double Interval => DeltaTime;
-
///
/// Creates a new difficulty hit object.
///
/// The gameplay associated with this difficulty object.
/// The gameplay preceding .
- /// The gameplay preceding .
/// The rate of the gameplay clock. Modified by speed-changing mods.
/// The list of all s in the current beatmap.
/// The list of centre (don) s in the current beatmap.
/// The list of rim (kat) s in the current beatmap.
/// The list of s that is a hit (i.e. not a drumroll or swell) in the current beatmap.
/// The position of this in the list.
- public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate,
+ /// The control point info of the beatmap.
+ /// The global slider velocity of the beatmap.
+ public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate,
List objects,
List centreHitObjects,
List rimHitObjects,
- List noteObjects, int index)
+ List noteObjects, int index,
+ ControlPointInfo controlPointInfo,
+ double globalSliderVelocity)
: base(hitObject, lastObject, clockRate, objects, index)
{
noteDifficultyHitObjects = noteObjects;
- // Create the Colour object, its properties should be filled in by TaikoDifficultyPreprocessor
- Colour = new TaikoDifficultyHitObjectColour();
+ ColourData = new TaikoColourData();
+ RhythmData = new TaikoRhythmData(this);
- // Create a Rhythm object, its properties are filled in by TaikoDifficultyHitObjectRhythm
- Rhythm = new TaikoDifficultyHitObjectRhythm(this);
-
- switch ((hitObject as Hit)?.Type)
+ if (hitObject is Hit hit)
{
- case HitType.Centre:
- MonoIndex = centreHitObjects.Count;
- centreHitObjects.Add(this);
- monoDifficultyHitObjects = centreHitObjects;
- break;
+ switch (hit.Type)
+ {
+ case HitType.Centre:
+ MonoIndex = centreHitObjects.Count;
+ centreHitObjects.Add(this);
+ monoDifficultyHitObjects = centreHitObjects;
+ break;
- case HitType.Rim:
- MonoIndex = rimHitObjects.Count;
- rimHitObjects.Add(this);
- monoDifficultyHitObjects = rimHitObjects;
- break;
- }
+ case HitType.Rim:
+ MonoIndex = rimHitObjects.Count;
+ rimHitObjects.Add(this);
+ monoDifficultyHitObjects = rimHitObjects;
+ break;
+ }
- if (hitObject is Hit)
- {
NoteIndex = noteObjects.Count;
noteObjects.Add(this);
}
+
+ // Using `hitObject.StartTime` causes floating point error differences
+ double normalisedStartTime = StartTime * clockRate;
+
+ // Retrieve the timing point at the note's start time
+ TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(normalisedStartTime);
+
+ // Calculate the slider velocity at the note's start time.
+ double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, normalisedStartTime, clockRate);
+
+ EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity;
+ }
+
+ ///
+ /// Calculates the slider velocity based on control point info and clock rate.
+ ///
+ private static double calculateSliderVelocity(ControlPointInfo controlPointInfo, double globalSliderVelocity, double startTime, double clockRate)
+ {
+ var activeEffectControlPoint = controlPointInfo.EffectPointAt(startTime);
+ return globalSliderVelocity * (activeEffectControlPoint.ScrollSpeed) * clockRate;
}
public TaikoDifficultyHitObject? PreviousMono(int backwardsIndex) => monoDifficultyHitObjects?.ElementAtOrDefault(MonoIndex - (backwardsIndex + 1));
@@ -120,5 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
public TaikoDifficultyHitObject? PreviousNote(int backwardsIndex) => noteDifficultyHitObjects.ElementAtOrDefault(NoteIndex - (backwardsIndex + 1));
public TaikoDifficultyHitObject? NextNote(int forwardsIndex) => noteDifficultyHitObjects.ElementAtOrDefault(NoteIndex + (forwardsIndex + 1));
+
+ public double Interval => DeltaTime;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Reading.cs
index 885131404a..7be1107b70 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Reading.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Reading.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
}
var taikoObject = (TaikoDifficultyHitObject)current;
- int index = taikoObject.Colour.MonoStreak?.HitObjects.IndexOf(taikoObject) ?? 0;
+ int index = taikoObject.ColourData.MonoStreak?.HitObjects.IndexOf(taikoObject) ?? 0;
currentStrain *= DifficultyCalculationUtils.Logistic(index, 4, -1 / 25.0, 0.5) + 0.5;
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
index 12e1396dd7..0e1f3d41cf 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
// Safely prevents previous strains from shifting as new notes are added.
var currentObject = current as TaikoDifficultyHitObject;
- int index = currentObject?.Colour.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0;
+ int index = currentObject?.ColourData.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0;
double monolengthBonus = isConvert ? 1 : 1 + Math.Min(Math.Max((index - 5) / 50.0, 0), 0.30);
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index f3b976f970..6b9986bd68 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -13,8 +13,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
-using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Reading;
-using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
+using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Scoring;
@@ -72,7 +71,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
var centreObjects = new List();
var rimObjects = new List();
var noteObjects = new List();
- EffectiveBPMPreprocessor bpmLoader = new EffectiveBPMPreprocessor(beatmap, noteObjects);
// Generate TaikoDifficultyHitObjects from the beatmap's hit objects.
for (int i = 2; i < beatmap.HitObjects.Count; i++)
@@ -80,21 +78,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
difficultyHitObjects.Add(new TaikoDifficultyHitObject(
beatmap.HitObjects[i],
beatmap.HitObjects[i - 1],
- beatmap.HitObjects[i - 2],
clockRate,
difficultyHitObjects,
centreObjects,
rimObjects,
noteObjects,
- difficultyHitObjects.Count
+ difficultyHitObjects.Count,
+ beatmap.ControlPointInfo,
+ beatmap.Difficulty.SliderMultiplier
));
}
- var groupedHitObjects = SameRhythmHitObjects.GroupHitObjects(noteObjects);
-
TaikoColourDifficultyPreprocessor.ProcessAndAssign(difficultyHitObjects);
- SamePatterns.GroupPatterns(groupedHitObjects);
- bpmLoader.ProcessEffectiveBPM(beatmap.ControlPointInfo, clockRate);
+ TaikoRhythmDifficultyPreprocessor.ProcessAndAssign(noteObjects);
return difficultyHitObjects;
}
@@ -204,9 +200,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
/// Applies a final re-scaling of the star rating.
///
/// The raw star rating value before re-scaling.
- private double rescale(double sr)
+ private static double rescale(double sr)
{
- if (sr < 0) return sr;
+ if (sr < 0)
+ return sr;
return 10.43 * Math.Log(sr / 8 + 1);
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs
similarity index 51%
rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs
rename to osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs
index 8f3917cbde..a42940180c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs
@@ -1,13 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
+namespace osu.Game.Rulesets.Taiko.Difficulty.Utils
{
///
- /// The interface for hitobjects that provide an interval value.
+ /// The interface for objects that provide an interval value.
///
public interface IHasInterval
{
+ ///
+ /// The interval – ie delta time – between this object and a known previous object.
+ ///
double Interval { get; }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs
new file mode 100644
index 0000000000..7bd7aa7677
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs
@@ -0,0 +1,59 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Utils;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Utils
+{
+ public static class IntervalGroupingUtils
+ {
+ public static List> GroupByInterval(IReadOnlyList objects) where T : IHasInterval
+ {
+ var groups = new List>();
+
+ int i = 0;
+ while (i < objects.Count)
+ groups.Add(createNextGroup(objects, ref i));
+
+ return groups;
+ }
+
+ private static List createNextGroup(IReadOnlyList objects, ref int i) where T : IHasInterval
+ {
+ const double margin_of_error = 5;
+
+ var groupedObjects = new List { objects[i] };
+ i++;
+
+ for (; i < objects.Count - 1; i++)
+ {
+ if (!Precision.AlmostEquals(objects[i].Interval, objects[i + 1].Interval, margin_of_error))
+ {
+ // When an interval change occurs, include the object with the differing interval in the case it increased
+ // See https://github.com/ppy/osu/pull/31636#discussion_r1942368372 for rationale.
+ if (objects[i + 1].Interval > objects[i].Interval + margin_of_error)
+ {
+ groupedObjects.Add(objects[i]);
+ i++;
+ }
+
+ return groupedObjects;
+ }
+
+ // No interval change occurred
+ groupedObjects.Add(objects[i]);
+ }
+
+ // Check if the last two objects in the object form a "flat" rhythm pattern within the specified margin of error.
+ // If true, add the current object to the group and increment the index to process the next object.
+ if (objects.Count > 2 && i < objects.Count && Precision.AlmostEquals(objects[^1].Interval, objects[^2].Interval, margin_of_error))
+ {
+ groupedObjects.Add(objects[i]);
+ i++;
+ }
+
+ return groupedObjects;
+ }
+ }
+}