From fa20bc6631b084b4fbd3b97c3cd257a005379b0e Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:24:04 +0000 Subject: [PATCH 01/23] Remove `EffectiveBPMPreprocessor` --- .../Preprocessing/Reading/EffectiveBPM.cs | 50 ------------------- .../Preprocessing/TaikoDifficultyHitObject.cs | 27 +++++++++- .../Difficulty/TaikoDifficultyCalculator.cs | 13 +++-- 3 files changed, 32 insertions(+), 58 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Reading/EffectiveBPM.cs 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/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index dfcd08ed94..34c4871a42 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -3,6 +3,7 @@ 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.Objects; @@ -76,11 +77,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing /// 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. + /// The control point info of the beatmap. + /// The global slider velocity of the beatmap. public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, 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; @@ -111,6 +116,26 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing NoteIndex = noteObjects.Count; noteObjects.Add(this); } + + double startTime = hitObject.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, globalSliderVelocity, startTime, clockRate); + CurrentSliderVelocity = currentSliderVelocity; + + 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)); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index f3b976f970..1d3075e4ac 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -13,7 +13,6 @@ 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.Skills; using osu.Game.Rulesets.Taiko.Mods; @@ -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++) @@ -86,15 +84,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty 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); + + var groupedHitObjects = SameRhythmGroupedHitObjects.GroupHitObjects(noteObjects); + SamePatternsGroupedHitObjects.GroupPatterns(groupedHitObjects); return difficultyHitObjects; } From dbe36887f6da2649e9c55e265d6e4eb15429929a Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:24:27 +0000 Subject: [PATCH 02/23] Refactor `ColourEvaluator` --- .../Difficulty/Evaluators/ColourEvaluator.cs | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs index 3ff5b87fb6..c0e90e83c1 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. @@ -89,18 +65,27 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators double difficulty = 0.0d; if (colour.MonoStreak?.FirstHitObject == hitObject) // Difficulty for MonoStreak - difficulty += EvaluateDifficultyOf(colour.MonoStreak); + difficulty += evaluateMonoStreakDifficulty(colour.MonoStreak); if (colour.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern - difficulty += EvaluateDifficultyOf(colour.AlternatingMonoPattern); + difficulty += evaluateAlternatingMonoPatternDifficulty(colour.AlternatingMonoPattern); if (colour.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern - difficulty += EvaluateDifficultyOf(colour.RepeatingHitPattern); + difficulty += evaluateReadingHitPatternDifficulty(colour.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) * evaluateReadingHitPatternDifficulty(alternatingMonoPattern.Parent); + + private static double evaluateReadingHitPatternDifficulty(RepeatingHitPatterns repeatingHitPattern) => + 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E)); } } From 9919179b0b914aba42499467cba38ee2d311034b Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:24:46 +0000 Subject: [PATCH 03/23] Format `ReadingEvaluator` --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; } From b8c79d58a731943f46433298db8eb0523ec850b7 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:25:28 +0000 Subject: [PATCH 04/23] Refactor `StaminaEvaluator` --- .../Difficulty/Evaluators/StaminaEvaluator.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs index b39ad953a4..a9884b2328 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs @@ -8,8 +8,34 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators { - public class StaminaEvaluator + public static class StaminaEvaluator { + /// + /// 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. + /// + public static double EvaluateDifficultyOf(DifficultyHitObject current) + { + if (current.BaseObject is not Hit) + { + return 0.0; + } + + // Find the previous hit object hit by the current finger, which is n notes prior, n being the number of + // available fingers. + TaikoDifficultyHitObject taikoCurrent = (TaikoDifficultyHitObject)current; + TaikoDifficultyHitObject? taikoPrevious = current.Previous(1) as TaikoDifficultyHitObject; + TaikoDifficultyHitObject? previousMono = taikoCurrent.PreviousMono(availableFingersFor(taikoCurrent) - 1); + + double objectStrain = 0.5; // Add a base strain to all objects + if (taikoPrevious == null) return objectStrain; + + if (previousMono != null) + objectStrain += speedBonus(taikoCurrent.StartTime - previousMono.StartTime) + 0.5 * speedBonus(taikoCurrent.StartTime - taikoPrevious.StartTime); + + return objectStrain; + } + /// /// Applies a speed bonus dependent on the time since the last hit performed using this finger. /// @@ -44,31 +70,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators 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. - /// - public static double EvaluateDifficultyOf(DifficultyHitObject current) - { - if (current.BaseObject is not Hit) - { - return 0.0; - } - - // Find the previous hit object hit by the current finger, which is n notes prior, n being the number of - // available fingers. - TaikoDifficultyHitObject taikoCurrent = (TaikoDifficultyHitObject)current; - TaikoDifficultyHitObject? taikoPrevious = current.Previous(1) as TaikoDifficultyHitObject; - TaikoDifficultyHitObject? previousMono = taikoCurrent.PreviousMono(availableFingersFor(taikoCurrent) - 1); - - double objectStrain = 0.5; // Add a base strain to all objects - if (taikoPrevious == null) return objectStrain; - - if (previousMono != null) - objectStrain += speedBonus(taikoCurrent.StartTime - previousMono.StartTime) + 0.5 * speedBonus(taikoCurrent.StartTime - taikoPrevious.StartTime); - - return objectStrain; - } } } From ef8867704adaeb813bce65fe1e44844aea86ddce Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:28:15 +0000 Subject: [PATCH 05/23] Add xmldoc to explain `IHasInterval.Interval` --- .../Difficulty/Preprocessing/Rhythm/IHasInterval.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs index 8f3917cbde..32b148da2e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs @@ -8,6 +8,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// public interface IHasInterval { + /// + /// The interval between 2 objects start times. + /// double Interval { get; } } } From 20a76d832df7986c623f9e7fecd468fc012782eb Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:29:07 +0000 Subject: [PATCH 06/23] Rename rhythm preprocessing objects to be clearer with intent --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 38 +++++++++---------- ...Rhythm.cs => IntervalGroupedHitObjects.cs} | 31 ++++++--------- ...ns.cs => SamePatternsGroupedHitObjects.cs} | 28 +++++++------- ...ects.cs => SameRhythmGroupedHitObjects.cs} | 30 +++++++-------- .../Rhythm/TaikoDifficultyHitObjectRhythm.cs | 4 +- 5 files changed, 60 insertions(+), 71 deletions(-) rename osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/{SameRhythm.cs => IntervalGroupedHitObjects.cs} (62%) rename osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/{SamePatterns.cs => SamePatternsGroupedHitObjects.cs} (50%) rename osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/{SameRhythmHitObjects.cs => SameRhythmGroupedHitObjects.cs} (70%) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs index 22321a8f6e..8accc6124c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs @@ -25,32 +25,32 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators double samePattern = 0; double intervalPenalty = 0; - if (rhythm.SameRhythmHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmHitObjects + if (rhythm.SameRhythmGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmGroupedHitObjects { - sameRhythm += 10.0 * evaluateDifficultyOf(rhythm.SameRhythmHitObjects, hitWindow); - intervalPenalty = repeatedIntervalPenalty(rhythm.SameRhythmHitObjects, hitWindow); + sameRhythm += 10.0 * evaluateDifficultyOf(rhythm.SameRhythmGroupedHitObjects, hitWindow); + intervalPenalty = repeatedIntervalPenalty(rhythm.SameRhythmGroupedHitObjects, hitWindow); } - if (rhythm.SamePatterns?.FirstHitObject == hitObject) // Difficulty for SamePatterns - samePattern += 1.15 * ratioDifficulty(rhythm.SamePatterns.IntervalRatio); + if (rhythm.SamePatternsGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SamePatternsGroupedHitObjects + samePattern += 1.15 * ratioDifficulty(rhythm.SamePatternsGroupedHitObjects.IntervalRatio); difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty; return difficulty; } - private static double evaluateDifficultyOf(SameRhythmHitObjects sameRhythmHitObjects, double hitWindow) + private static double evaluateDifficultyOf(SameRhythmGroupedHitObjects 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.Children.Count > 1) { - double expectedDurationFromPrevious = (double)previousInterval * sameRhythmHitObjects.Children.Count; - double durationDifference = sameRhythmHitObjects.Duration - expectedDurationFromPrevious; + double expectedDurationFromPrevious = (double)previousInterval * sameRhythmGroupedHitObjects.Children.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(SameRhythmGroupedHitObjects 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.Children.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(SameRhythmGroupedHitObjects startObject, int intervalCount) { List intervals = new List(); var currentObject = startObject; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs similarity index 62% rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythm.cs rename to osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs index b1ca22595b..930b3fc0e4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs @@ -1,8 +1,8 @@ // 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; +using osu.Framework.Utils; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data { @@ -10,35 +10,26 @@ 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 abstract class IntervalGroupedHitObjects + where TChildType : IHasInterval { - public IReadOnlyList Children { get; private set; } + 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 + /// 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 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) + protected IntervalGroupedHitObjects(List data, ref int i, double marginOfError) { - List children = new List(); + List children = new List(); Children = children; children.Add(data[i]); i++; @@ -46,9 +37,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data 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 (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, marginOfError)) { - if (data[i + 1].Interval > data[i].Interval + marginOfError) + if (Precision.DefinitelyBigger(data[i].Interval, data[i + 1].Interval, marginOfError)) { children.Add(data[i]); i++; @@ -63,7 +54,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data // 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)) + if (data.Count > 2 && Precision.AlmostEquals(data[^1].Interval, data[^2].Interval, marginOfError)) { children.Add(data[i]); i++; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatterns.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs similarity index 50% rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatterns.cs rename to osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs index 50839c4561..d4cbc9c1f9 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatterns.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs @@ -7,21 +7,21 @@ using System.Linq; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data { /// - /// Represents grouped by their 's interval. + /// Represents grouped by their 's interval. /// - public class SamePatterns : SameRhythm + public class SamePatternsGroupedHitObjects : IntervalGroupedHitObjects { - public SamePatterns? Previous { get; private set; } + public SamePatternsGroupedHitObjects? 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 . + /// 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. + /// 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; @@ -29,26 +29,26 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data public IEnumerable AllHitObjects => Children.SelectMany(child => child.Children); - private SamePatterns(SamePatterns? previous, List data, ref int i) + private SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List data, ref int i) : base(data, ref i, 5) { Previous = previous; foreach (TaikoDifficultyHitObject hitObject in AllHitObjects) { - hitObject.Rhythm.SamePatterns = this; + hitObject.Rhythm.SamePatternsGroupedHitObjects = this; } } - public static void GroupPatterns(List data) + public static void GroupPatterns(List data) { - List samePatterns = new List(); + List samePatterns = new List(); - // Index does not need to be incremented, as it is handled within the SameRhythm constructor. + // Index does not need to be incremented, as it is handled within the IntervalGroupedHitObjects constructor. for (int i = 0; i < data.Count;) { - SamePatterns? previous = samePatterns.Count > 0 ? samePatterns[^1] : null; - samePatterns.Add(new SamePatterns(previous, data, ref i)); + SamePatternsGroupedHitObjects? previous = samePatterns.Count > 0 ? samePatterns[^1] : null; + samePatterns.Add(new SamePatternsGroupedHitObjects(previous, data, ref i)); } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs similarity index 70% rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjects.cs rename to osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs index 0ccc6da026..0b59433a2e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs @@ -9,11 +9,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data /// /// Represents a group of s with no rhythm variation. /// - public class SameRhythmHitObjects : SameRhythm, IHasInterval + public class SameRhythmGroupedHitObjects : IntervalGroupedHitObjects, IHasInterval { public TaikoDifficultyHitObject FirstHitObject => Children[0]; - public SameRhythmHitObjects? Previous; + public SameRhythmGroupedHitObjects? Previous; /// /// of the first hit object. @@ -26,30 +26,28 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data 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 . + /// 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 + /// 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 double Interval { get; private set; } - public SameRhythmHitObjects(SameRhythmHitObjects? previous, List data, ref int i) + public SameRhythmGroupedHitObjects(SameRhythmGroupedHitObjects? previous, List data, ref int i) : base(data, ref i, 5) { Previous = previous; foreach (var hitObject in Children) { - hitObject.Rhythm.SameRhythmHitObjects = this; + hitObject.Rhythm.SameRhythmGroupedHitObjects = this; // Pass the HitObjectInterval to each child. hitObject.HitObjectInterval = HitObjectInterval; @@ -58,15 +56,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data calculateIntervals(); } - public static List GroupHitObjects(List data) + public static List GroupHitObjects(List data) { - List flatPatterns = new List(); + List flatPatterns = new List(); - // Index does not need to be incremented, as it is handled within SameRhythm's constructor. + // Index does not need to be incremented, as it is handled within IntervalGroupedHitObjects'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)); + SameRhythmGroupedHitObjects? previous = flatPatterns.Count > 0 ? flatPatterns[^1] : null; + flatPatterns.Add(new SameRhythmGroupedHitObjects(previous, data, ref i)); } return flatPatterns; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs index beb7bfe5f6..351015ae08 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// /// The group of hit objects with consistent rhythm that this object belongs to. /// - public SameRhythmHitObjects? SameRhythmHitObjects; + public SameRhythmGroupedHitObjects? SameRhythmGroupedHitObjects; /// /// The larger pattern of rhythm groups that this object is part of. /// - public SamePatterns? SamePatterns; + public SamePatternsGroupedHitObjects? SamePatternsGroupedHitObjects; /// /// The ratio of current From e0882d2a53d5452bb539bb9b16a0019b3f4094d2 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:33:40 +0000 Subject: [PATCH 07/23] Make `rescale` a static method --- .../Difficulty/TaikoDifficultyCalculator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 1d3075e4ac..e07a965ab0 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -203,9 +203,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); } From 764b0001efc8ec7bc9aff48c525ee78f47b468aa Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 14:56:51 +0000 Subject: [PATCH 08/23] Fix typo in `ColourEvaluator` --- .../Difficulty/Evaluators/ColourEvaluator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs index c0e90e83c1..166c01f507 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators difficulty += evaluateAlternatingMonoPatternDifficulty(colour.AlternatingMonoPattern); if (colour.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern - difficulty += evaluateReadingHitPatternDifficulty(colour.RepeatingHitPattern); + difficulty += evaluateRepeatingHitPatternsDifficulty(colour.RepeatingHitPattern); double consistencyPenalty = consistentRatioPenalty(taikoObject); difficulty *= consistencyPenalty; @@ -83,9 +83,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators 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) * evaluateReadingHitPatternDifficulty(alternatingMonoPattern.Parent); + DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * evaluateRepeatingHitPatternsDifficulty(alternatingMonoPattern.Parent); - private static double evaluateReadingHitPatternDifficulty(RepeatingHitPatterns repeatingHitPattern) => + private static double evaluateRepeatingHitPatternsDifficulty(RepeatingHitPatterns repeatingHitPattern) => 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E)); } } From 1c4bc6dffd64126ab1b380ab0e6d11ff17c16a32 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 15:00:23 +0000 Subject: [PATCH 09/23] Revert `Precision.DefinitelyBigger` usage --- .../Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs index 930b3fc0e4..cc389d4091 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data // An interval change occured, add the current data if the next interval is larger. if (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, marginOfError)) { - if (Precision.DefinitelyBigger(data[i].Interval, data[i + 1].Interval, marginOfError)) + if (data[i + 1].Interval > data[i].Interval + marginOfError) { children.Add(data[i]); i++; From 14c68bcc583d1e980225da3f022176412ede3cb8 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 21 Jan 2025 15:58:33 +0000 Subject: [PATCH 10/23] Replace weird `IntervalGroupedHitObjects` inheritance layer --- .../Rhythm/Data/IntervalGroupedHitObjects.cs | 64 ------------------- .../Data/SamePatternsGroupedHitObjects.cs | 27 ++------ .../Data/SameRhythmGroupedHitObjects.cs | 57 ++++------------- .../TaikoRhythmDifficultyPreprocessor.cs | 63 ++++++++++++++++++ .../Preprocessing/TaikoDifficultyHitObject.cs | 1 + .../Difficulty/TaikoDifficultyCalculator.cs | 6 +- .../Rhythm => Utils}/IHasInterval.cs | 4 +- .../Difficulty/Utils/IntervalGroupingUtils.cs | 64 +++++++++++++++++++ 8 files changed, 152 insertions(+), 134 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs rename osu.Game.Rulesets.Taiko/Difficulty/{Preprocessing/Rhythm => Utils}/IHasInterval.cs (73%) create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs deleted file mode 100644 index cc389d4091..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/IntervalGroupedHitObjects.cs +++ /dev/null @@ -1,64 +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.Framework.Utils; - -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 IntervalGroupedHitObjects - where TChildType : IHasInterval - { - public IReadOnlyList Children { get; private set; } - - /// - /// 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 IntervalGroupedHitObjects(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 (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, 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 && Precision.AlmostEquals(data[^1].Interval, data[^2].Interval, marginOfError)) - { - children.Add(data[i]); - 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 index d4cbc9c1f9..cb22b2ef82 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs @@ -9,9 +9,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data /// /// Represents grouped by their 's interval. /// - public class SamePatternsGroupedHitObjects : IntervalGroupedHitObjects + public class SamePatternsGroupedHitObjects { - public SamePatternsGroupedHitObjects? Previous { get; private set; } + public IReadOnlyList Children { get; } + + public SamePatternsGroupedHitObjects? Previous { get; } /// /// The between children within this group. @@ -29,27 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data public IEnumerable AllHitObjects => Children.SelectMany(child => child.Children); - private SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List data, ref int i) - : base(data, ref i, 5) + public SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List children) { Previous = previous; - - foreach (TaikoDifficultyHitObject hitObject in AllHitObjects) - { - hitObject.Rhythm.SamePatternsGroupedHitObjects = this; - } - } - - public static void GroupPatterns(List data) - { - List samePatterns = new List(); - - // Index does not need to be incremented, as it is handled within the IntervalGroupedHitObjects constructor. - for (int i = 0; i < data.Count;) - { - SamePatternsGroupedHitObjects? previous = samePatterns.Count > 0 ? samePatterns[^1] : null; - samePatterns.Add(new SamePatternsGroupedHitObjects(previous, data, ref i)); - } + Children = children; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs index 0b59433a2e..dc6cf45d23 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs @@ -3,14 +3,17 @@ 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 SameRhythmGroupedHitObjects : IntervalGroupedHitObjects, IHasInterval + public class SameRhythmGroupedHitObjects : IHasInterval { + public List Children { get; private set; } + public TaikoDifficultyHitObject FirstHitObject => Children[0]; public SameRhythmGroupedHitObjects? Previous; @@ -40,53 +43,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data /// public double Interval { get; private set; } - public SameRhythmGroupedHitObjects(SameRhythmGroupedHitObjects? previous, List data, ref int i) - : base(data, ref i, 5) + public SameRhythmGroupedHitObjects(SameRhythmGroupedHitObjects? previous, List children) { Previous = previous; + Children = children; - foreach (var hitObject in Children) - { - hitObject.Rhythm.SameRhythmGroupedHitObjects = this; + // Calculate the average interval between hitobjects, or null if there are fewer than two + HitObjectInterval = Children.Count < 2 ? null : Duration / (Children.Count - 1); - // Pass the HitObjectInterval to each child. - hitObject.HitObjectInterval = HitObjectInterval; - } + // 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; - calculateIntervals(); - } - - public static List GroupHitObjects(List data) - { - List flatPatterns = new List(); - - // Index does not need to be incremented, as it is handled within IntervalGroupedHitObjects's constructor. - for (int i = 0; i < data.Count;) - { - SameRhythmGroupedHitObjects? previous = flatPatterns.Count > 0 ? flatPatterns[^1] : null; - flatPatterns.Add(new SameRhythmGroupedHitObjects(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; + // Calculate the interval from the previous group's start time + Interval = Previous != null ? StartTime - Previous.StartTime : 0; } } } 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..fa2135caf3 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.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.Taiko.Difficulty.Preprocessing.Rhythm.Data; + +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.Children) + { + hitObject.Rhythm.SameRhythmGroupedHitObjects = rhythmGroup; + hitObject.HitObjectInterval = rhythmGroup.HitObjectInterval; + } + } + + var patternGroups = createSamePatternGroupedHitObjects(rhythmGroups); + + foreach (var patternGroup in patternGroups) + { + foreach (var hitObject in patternGroup.AllHitObjects) + { + hitObject.Rhythm.SamePatternsGroupedHitObjects = patternGroup; + } + } + } + + private static List createSameRhythmGroupedHitObjects(List hitObjects) + { + var rhythmGroups = new List(); + var groups = IntervalGroupingUtils.GroupByInterval(hitObjects); + + foreach (var group in groups) + { + var previous = rhythmGroups.Count > 0 ? rhythmGroups[^1] : null; + rhythmGroups.Add(new SameRhythmGroupedHitObjects(previous, group)); + } + + return rhythmGroups; + } + + private static List createSamePatternGroupedHitObjects(List rhythmGroups) + { + var patternGroups = new List(); + var groups = IntervalGroupingUtils.GroupByInterval(rhythmGroups); + + foreach (var group in groups) + { + var previous = patternGroups.Count > 0 ? patternGroups[^1] : null; + patternGroups.Add(new SamePatternsGroupedHitObjects(previous, group)); + } + + return patternGroups; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 34c4871a42..0c668797cd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects; 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 { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index e07a965ab0..acd654f9b8 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -13,7 +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.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; @@ -91,9 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty } TaikoColourDifficultyPreprocessor.ProcessAndAssign(difficultyHitObjects); - - var groupedHitObjects = SameRhythmGroupedHitObjects.GroupHitObjects(noteObjects); - SamePatternsGroupedHitObjects.GroupPatterns(groupedHitObjects); + TaikoRhythmDifficultyPreprocessor.ProcessAndAssign(noteObjects); return difficultyHitObjects; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs similarity index 73% rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs rename to osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs index 32b148da2e..8f80bb6079 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/IHasInterval.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs @@ -1,10 +1,10 @@ // 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 { 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..22ded8a966 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs @@ -0,0 +1,64 @@ +// 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; +using osu.Game.Rulesets.Taiko.Difficulty.Utils; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data +{ + public static class IntervalGroupingUtils + { + public static List> GroupByInterval(IReadOnlyList data, double marginOfError = 5) where T : IHasInterval + { + var groups = new List>(); + if (data.Count == 0) + return groups; + + int i = 0; + + while (i < data.Count) + { + var group = createGroup(data, ref i, marginOfError); + groups.Add(group); + } + + return groups; + } + + private static List createGroup(IReadOnlyList data, ref int i, double marginOfError) where T : IHasInterval + { + var children = new List { data[i] }; + i++; + + for (; i < data.Count - 1; i++) + { + // An interval change occured, add the current data if the next interval is larger. + if (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, marginOfError)) + { + if (data[i + 1].Interval > data[i].Interval + marginOfError) + { + children.Add(data[i]); + i++; + } + + return children; + } + + // No interval change occurred + 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 && i < data.Count && + Precision.AlmostEquals(data[^1].Interval, data[^2].Interval, marginOfError)) + { + children.Add(data[i]); + i++; + } + + return children; + } + } +} From 2c0d6b14c82969a850b292f785a678016e06ed26 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 22 Jan 2025 13:24:30 +0000 Subject: [PATCH 11/23] Fix incorrect namespace --- .../Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs | 1 + .../Difficulty/Utils/IntervalGroupingUtils.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs index fa2135caf3..cd56d835dc 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data; +using osu.Game.Rulesets.Taiko.Difficulty.Utils; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs index 22ded8a966..3b6f5406b4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs @@ -3,9 +3,8 @@ using System.Collections.Generic; using osu.Framework.Utils; -using osu.Game.Rulesets.Taiko.Difficulty.Utils; -namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data +namespace osu.Game.Rulesets.Taiko.Difficulty.Utils { public static class IntervalGroupingUtils { From 753e9ef7c79f85d027557295c0c60fb4fa09210c Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 22 Jan 2025 13:26:12 +0000 Subject: [PATCH 12/23] Keep old behaviour of `double.PositiveInfinity` being the default for `Interval` --- .../Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs index dc6cf45d23..4f7023059f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data : 1; // Calculate the interval from the previous group's start time - Interval = Previous != null ? StartTime - Previous.StartTime : 0; + Interval = Previous != null ? StartTime - Previous.StartTime : double.PositiveInfinity; } } } From 8f17a44976439ba30c8ee13f1200d72821847c5a Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 23 Jan 2025 10:29:04 +0000 Subject: [PATCH 13/23] Remove unused default value --- .../Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs index 4f7023059f..b77176b49d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data /// 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; + public double HitObjectIntervalRatio; /// public double Interval { get; private set; } From a7aa553445738068eb8075043cb64187ed6b73dc Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 26 Jan 2025 16:21:07 +0000 Subject: [PATCH 14/23] Fix incorrect `startTime` calculation --- .../Difficulty/Preprocessing/TaikoDifficultyHitObject.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 0c668797cd..486841b995 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -118,13 +118,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing noteObjects.Add(this); } - double startTime = hitObject.StartTime * clockRate; - // Retrieve the timing point at the note's start time - TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(startTime); + TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(hitObject.StartTime); // Calculate the slider velocity at the note's start time. - double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, startTime, clockRate); + double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, hitObject.StartTime, clockRate); CurrentSliderVelocity = currentSliderVelocity; EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity; From 13c956c2482ee8ff81e83f283de9f17910ad189d Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 26 Jan 2025 20:15:13 +0000 Subject: [PATCH 15/23] Account for floating point errors --- .../Difficulty/Preprocessing/TaikoDifficultyHitObject.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 486841b995..f9ca2707ab 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -118,11 +118,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing noteObjects.Add(this); } + // Using `hitObject.StartTime` causes floating point error differences + double normalizedStartTime = StartTime * clockRate; + // Retrieve the timing point at the note's start time - TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(hitObject.StartTime); + TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(normalizedStartTime); // Calculate the slider velocity at the note's start time. - double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, hitObject.StartTime, clockRate); + double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, normalizedStartTime, clockRate); CurrentSliderVelocity = currentSliderVelocity; EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity; From 71b89c390fe7d672ec8f1f61bbea31352315a4fb Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Mon, 27 Jan 2025 12:54:22 +0000 Subject: [PATCH 16/23] Rename class, rename children to hit objects and groups, make fields un-settable --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 12 ++++---- .../Data/SamePatternsGroupedHitObjects.cs | 22 +++++++------- ...ects.cs => SameRhythmHitObjectGrouping.cs} | 30 +++++++++---------- .../Rhythm/TaikoDifficultyHitObjectRhythm.cs | 2 +- .../TaikoRhythmDifficultyPreprocessor.cs | 10 +++---- 5 files changed, 38 insertions(+), 38 deletions(-) rename osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/{SameRhythmGroupedHitObjects.cs => SameRhythmHitObjectGrouping.cs} (65%) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs index 8accc6124c..f4686f2fe3 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators return difficulty; } - private static double evaluateDifficultyOf(SameRhythmGroupedHitObjects sameRhythmGroupedHitObjects, double hitWindow) + private static double evaluateDifficultyOf(SameRhythmHitObjectGrouping sameRhythmGroupedHitObjects, double hitWindow) { double intervalDifficulty = ratioDifficulty(sameRhythmGroupedHitObjects.HitObjectIntervalRatio); double? previousInterval = sameRhythmGroupedHitObjects.Previous?.HitObjectInterval; @@ -47,9 +47,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators intervalDifficulty *= repeatedIntervalPenalty(sameRhythmGroupedHitObjects, hitWindow); // If a previous interval exists and there are multiple hit objects in the sequence: - if (previousInterval != null && sameRhythmGroupedHitObjects.Children.Count > 1) + if (previousInterval != null && sameRhythmGroupedHitObjects.HitObjects.Count > 1) { - double expectedDurationFromPrevious = (double)previousInterval * sameRhythmGroupedHitObjects.Children.Count; + double expectedDurationFromPrevious = (double)previousInterval * sameRhythmGroupedHitObjects.HitObjects.Count; double durationDifference = sameRhythmGroupedHitObjects.Duration - expectedDurationFromPrevious; if (durationDifference > 0) @@ -75,11 +75,11 @@ 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(SameRhythmGroupedHitObjects sameRhythmGroupedHitObjects, double hitWindow, double threshold = 0.1) + private static double repeatedIntervalPenalty(SameRhythmHitObjectGrouping sameRhythmGroupedHitObjects, double hitWindow, double threshold = 0.1) { double longIntervalPenalty = sameInterval(sameRhythmGroupedHitObjects, 3); - double shortIntervalPenalty = sameRhythmGroupedHitObjects.Children.Count < 6 + 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. @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators return Math.Min(longIntervalPenalty, shortIntervalPenalty) * durationPenalty; - double sameInterval(SameRhythmGroupedHitObjects startObject, int intervalCount) + double sameInterval(SameRhythmHitObjectGrouping startObject, int intervalCount) { List intervals = new List(); var currentObject = startObject; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs index cb22b2ef82..938cb4670f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SamePatternsGroupedHitObjects.cs @@ -7,34 +7,34 @@ using System.Linq; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data { /// - /// Represents grouped by their 's interval. + /// Represents grouped by their 's interval. /// public class SamePatternsGroupedHitObjects { - public IReadOnlyList Children { get; } + public IReadOnlyList Groups { get; } public SamePatternsGroupedHitObjects? Previous { get; } /// - /// The between children within this group. - /// If there is only one child, this will have the value of the first child's . + /// The between groups . + /// If there is only one group, this will have the value of the first group's . /// - public double ChildrenInterval => Children.Count > 1 ? Children[1].Interval : Children[0].Interval; + public double GroupInterval => Groups.Count > 1 ? Groups[1].Interval : Groups[0].Interval; /// - /// The ratio of between this and the previous . In the + /// 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 double IntervalRatio => GroupInterval / Previous?.GroupInterval ?? 1.0d; - public TaikoDifficultyHitObject FirstHitObject => Children[0].FirstHitObject; + public TaikoDifficultyHitObject FirstHitObject => Groups[0].FirstHitObject; - public IEnumerable AllHitObjects => Children.SelectMany(child => child.Children); + public IEnumerable AllHitObjects => Groups.SelectMany(hitObject => hitObject.HitObjects); - public SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List children) + public SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List groups) { Previous = previous; - Children = children; + Groups = groups; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjectGrouping.cs similarity index 65% rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs rename to osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjectGrouping.cs index b77176b49d..9caa9b9958 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmGroupedHitObjects.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/Data/SameRhythmHitObjectGrouping.cs @@ -10,46 +10,46 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data /// /// Represents a group of s with no rhythm variation. /// - public class SameRhythmGroupedHitObjects : IHasInterval + public class SameRhythmHitObjectGrouping : IHasInterval { - public List Children { get; private set; } + public readonly List HitObjects; - public TaikoDifficultyHitObject FirstHitObject => Children[0]; + public TaikoDifficultyHitObject FirstHitObject => HitObjects[0]; - public SameRhythmGroupedHitObjects? Previous; + public readonly SameRhythmHitObjectGrouping? Previous; /// /// of the first hit object. /// - public double StartTime => Children[0].StartTime; + public double StartTime => HitObjects[0].StartTime; /// /// The interval between the first and final hit object within this group. /// - public double Duration => Children[^1].StartTime - Children[0].StartTime; + 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 . + /// 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; + public readonly double? HitObjectInterval; /// - /// The ratio of between this and the previous . In the + /// 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; + public readonly double HitObjectIntervalRatio; /// - public double Interval { get; private set; } + public double Interval { get; } - public SameRhythmGroupedHitObjects(SameRhythmGroupedHitObjects? previous, List children) + public SameRhythmHitObjectGrouping(SameRhythmHitObjectGrouping? previous, List hitObjects) { Previous = previous; - Children = children; + HitObjects = hitObjects; // Calculate the average interval between hitobjects, or null if there are fewer than two - HitObjectInterval = Children.Count < 2 ? null : Duration / (Children.Count - 1); + 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 diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs index 351015ae08..3503a836fa 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// /// The group of hit objects with consistent rhythm that this object belongs to. /// - public SameRhythmGroupedHitObjects? SameRhythmGroupedHitObjects; + public SameRhythmHitObjectGrouping? SameRhythmGroupedHitObjects; /// /// The larger pattern of rhythm groups that this object is part of. diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs index cd56d835dc..3ebc0c25b7 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm foreach (var rhythmGroup in rhythmGroups) { - foreach (var hitObject in rhythmGroup.Children) + foreach (var hitObject in rhythmGroup.HitObjects) { hitObject.Rhythm.SameRhythmGroupedHitObjects = rhythmGroup; hitObject.HitObjectInterval = rhythmGroup.HitObjectInterval; @@ -33,21 +33,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm } } - private static List createSameRhythmGroupedHitObjects(List hitObjects) + private static List createSameRhythmGroupedHitObjects(List hitObjects) { - var rhythmGroups = new List(); + var rhythmGroups = new List(); var groups = IntervalGroupingUtils.GroupByInterval(hitObjects); foreach (var group in groups) { var previous = rhythmGroups.Count > 0 ? rhythmGroups[^1] : null; - rhythmGroups.Add(new SameRhythmGroupedHitObjects(previous, group)); + rhythmGroups.Add(new SameRhythmHitObjectGrouping(previous, group)); } return rhythmGroups; } - private static List createSamePatternGroupedHitObjects(List rhythmGroups) + private static List createSamePatternGroupedHitObjects(List rhythmGroups) { var patternGroups = new List(); var groups = IntervalGroupingUtils.GroupByInterval(rhythmGroups); From f3c17f1c2b73e4f12fd00b130bd8326ca17a74e6 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Mon, 27 Jan 2025 12:56:33 +0000 Subject: [PATCH 17/23] Use correct English --- .../Difficulty/Preprocessing/TaikoDifficultyHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index f9ca2707ab..d6a2d5874e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -119,13 +119,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing } // Using `hitObject.StartTime` causes floating point error differences - double normalizedStartTime = StartTime * clockRate; + double normalisedStartTime = StartTime * clockRate; // Retrieve the timing point at the note's start time - TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(normalizedStartTime); + TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(normalisedStartTime); // Calculate the slider velocity at the note's start time. - double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, normalizedStartTime, clockRate); + double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, normalisedStartTime, clockRate); CurrentSliderVelocity = currentSliderVelocity; EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity; From fa844b0ebc783222beadd1e6889dada450823219 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2025 15:01:59 +0900 Subject: [PATCH 18/23] Rename `Colour` / `Rhythm` related fields and classes --- .../Difficulty/Evaluators/ColourEvaluator.cs | 18 +++++----- .../Difficulty/Evaluators/RhythmEvaluator.cs | 12 +++---- .../Difficulty/Evaluators/StaminaEvaluator.cs | 4 +-- ...yHitObjectColour.cs => TaikoColourData.cs} | 2 +- .../TaikoColourDifficultyPreprocessor.cs | 10 +++--- ...yHitObjectRhythm.cs => TaikoRhythmData.cs} | 35 +++++++++---------- .../TaikoRhythmDifficultyPreprocessor.cs | 4 +-- .../Preprocessing/TaikoDifficultyHitObject.cs | 8 ++--- .../Difficulty/Skills/Reading.cs | 2 +- .../Difficulty/Skills/Stamina.cs | 2 +- 10 files changed, 48 insertions(+), 49 deletions(-) rename osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Colour/{TaikoDifficultyHitObjectColour.cs => TaikoColourData.cs} (96%) rename osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/{TaikoDifficultyHitObjectRhythm.cs => TaikoRhythmData.cs} (75%) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs index 166c01f507..b715dfc37a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs @@ -34,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) @@ -61,17 +61,17 @@ 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 += evaluateMonoStreakDifficulty(colour.MonoStreak); + if (colourData.MonoStreak?.FirstHitObject == hitObject) // Difficulty for MonoStreak + difficulty += evaluateMonoStreakDifficulty(colourData.MonoStreak); - if (colour.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern - difficulty += evaluateAlternatingMonoPatternDifficulty(colour.AlternatingMonoPattern); + if (colourData.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern + difficulty += evaluateAlternatingMonoPatternDifficulty(colourData.AlternatingMonoPattern); - if (colour.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern - difficulty += evaluateRepeatingHitPatternsDifficulty(colour.RepeatingHitPattern); + if (colourData.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern + difficulty += evaluateRepeatingHitPatternsDifficulty(colourData.RepeatingHitPattern); double consistencyPenalty = consistentRatioPenalty(taikoObject); difficulty *= consistencyPenalty; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs index f4686f2fe3..3b3aea07f3 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs @@ -18,21 +18,21 @@ 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.SameRhythmGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmGroupedHitObjects + if (rhythmData.SameRhythmGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmGroupedHitObjects { - sameRhythm += 10.0 * evaluateDifficultyOf(rhythm.SameRhythmGroupedHitObjects, hitWindow); - intervalPenalty = repeatedIntervalPenalty(rhythm.SameRhythmGroupedHitObjects, hitWindow); + sameRhythm += 10.0 * evaluateDifficultyOf(rhythmData.SameRhythmGroupedHitObjects, hitWindow); + intervalPenalty = repeatedIntervalPenalty(rhythmData.SameRhythmGroupedHitObjects, hitWindow); } - if (rhythm.SamePatternsGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SamePatternsGroupedHitObjects - samePattern += 1.15 * ratioDifficulty(rhythm.SamePatternsGroupedHitObjects.IntervalRatio); + if (rhythmData.SamePatternsGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SamePatternsGroupedHitObjects + samePattern += 1.15 * ratioDifficulty(rhythmData.SamePatternsGroupedHitObjects.IntervalRatio); difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs index a9884b2328..32ed8ec189 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/StaminaEvaluator.cs @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators /// private static int availableFingersFor(TaikoDifficultyHitObject hitObject) { - DifficultyHitObject? previousColourChange = hitObject.Colour.PreviousColourChange; - DifficultyHitObject? nextColourChange = hitObject.Colour.NextColourChange; + DifficultyHitObject? previousColourChange = hitObject.ColourData.PreviousColourChange; + DifficultyHitObject? nextColourChange = hitObject.ColourData.NextColourChange; if (previousColourChange != null && hitObject.StartTime - previousColourChange.StartTime < 300) { 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/Rhythm/TaikoDifficultyHitObjectRhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs similarity index 75% rename from osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs rename to osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs index 3503a836fa..d895dcfc55 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoDifficultyHitObjectRhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// /// Stores rhythm data for a . /// - public class TaikoDifficultyHitObjectRhythm + public class TaikoRhythmData { /// /// The group of hit objects with consistent rhythm that this object belongs to. @@ -39,25 +39,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// speeding up is generally harder than slowing down (with exceptions of rhythm changes requiring a hand switch). /// /// - private static readonly TaikoDifficultyHitObjectRhythm[] common_rhythms = + private static readonly TaikoRhythmData[] 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) + new TaikoRhythmData(1, 1), + new TaikoRhythmData(2, 1), + new TaikoRhythmData(1, 2), + new TaikoRhythmData(3, 1), + new TaikoRhythmData(1, 3), + new TaikoRhythmData(3, 2), + new TaikoRhythmData(2, 3), + new TaikoRhythmData(5, 4), + new TaikoRhythmData(4, 5) }; /// - /// Initialises a new instance of s, + /// 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) + public TaikoRhythmData(TaikoDifficultyHitObject current) { var previous = current.Previous(0); @@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm return; } - TaikoDifficultyHitObjectRhythm closestRhythm = getClosestRhythm(current.DeltaTime, previous.DeltaTime); - Ratio = closestRhythm.Ratio; + TaikoRhythmData closestRhythmData = getClosestRhythm(current.DeltaTime, previous.DeltaTime); + Ratio = closestRhythmData.Ratio; } /// @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// /// The numerator for . /// The denominator for - private TaikoDifficultyHitObjectRhythm(int numerator, int denominator) + private TaikoRhythmData(int numerator, int denominator) { Ratio = numerator / (double)denominator; } @@ -88,11 +88,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// 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) + private TaikoRhythmData 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/TaikoRhythmDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs index 3ebc0c25b7..45cc29c99e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm { foreach (var hitObject in rhythmGroup.HitObjects) { - hitObject.Rhythm.SameRhythmGroupedHitObjects = rhythmGroup; + hitObject.RhythmData.SameRhythmGroupedHitObjects = rhythmGroup; hitObject.HitObjectInterval = rhythmGroup.HitObjectInterval; } } @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm { foreach (var hitObject in patternGroup.AllHitObjects) { - hitObject.Rhythm.SamePatternsGroupedHitObjects = patternGroup; + hitObject.RhythmData.SamePatternsGroupedHitObjects = patternGroup; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index d6a2d5874e..5c5503c25d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing /// /// The rhythm required to hit this hit object. /// - public readonly TaikoDifficultyHitObjectRhythm Rhythm; + public readonly TaikoRhythmData RhythmData; /// /// The interval between this hit object and the surrounding hit objects in its rhythm group. @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing /// 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. @@ -92,10 +92,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing noteDifficultyHitObjects = noteObjects; // Create the Colour object, its properties should be filled in by TaikoDifficultyPreprocessor - Colour = new TaikoDifficultyHitObjectColour(); + ColourData = new TaikoColourData(); // Create a Rhythm object, its properties are filled in by TaikoDifficultyHitObjectRhythm - Rhythm = new TaikoDifficultyHitObjectRhythm(this); + RhythmData = new TaikoRhythmData(this); switch ((hitObject as Hit)?.Type) { 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); From 709ad02a517606b07b6a4aaf3f55e611a94219c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2025 15:09:51 +0900 Subject: [PATCH 19/23] Simplify `TaikoRhythmData`'s ratio computation --- .../Preprocessing/Rhythm/TaikoRhythmData.cs | 68 +++++++------------ 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs index d895dcfc55..6c4a332624 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmData.cs @@ -27,30 +27,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm /// 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). - /// + /// This is snapped to the closest matching . /// - private static readonly TaikoRhythmData[] common_rhythms = - { - new TaikoRhythmData(1, 1), - new TaikoRhythmData(2, 1), - new TaikoRhythmData(1, 2), - new TaikoRhythmData(3, 1), - new TaikoRhythmData(1, 3), - new TaikoRhythmData(3, 2), - new TaikoRhythmData(2, 3), - new TaikoRhythmData(5, 4), - new TaikoRhythmData(4, 5) - }; + public readonly double Ratio; /// /// Initialises a new instance of s, @@ -67,31 +47,33 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm return; } - TaikoRhythmData closestRhythmData = getClosestRhythm(current.DeltaTime, previous.DeltaTime); - Ratio = closestRhythmData.Ratio; + double actualRatio = current.DeltaTime / previous.DeltaTime; + double closestRatio = common_ratios.OrderBy(r => Math.Abs(r - actualRatio)).First(); + + Ratio = closestRatio; } /// - /// Creates an object representing a rhythm change. + /// List of most common rhythm changes in taiko maps. Based on how each object's interval compares to the previous object. /// - /// The numerator for . - /// The denominator for - private TaikoRhythmData(int numerator, int denominator) + /// + /// 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[] { - 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 TaikoRhythmData getClosestRhythm(double currentDeltaTime, double previousDeltaTime) - { - double ratio = currentDeltaTime / previousDeltaTime; - return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First(); - } + 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 + }; } } From fc933902844ce21ffa6961920dd96bbe47d94fa1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2025 15:10:15 +0900 Subject: [PATCH 20/23] Remove unused `HitObjectInterval` --- .../Rhythm/TaikoRhythmDifficultyPreprocessor.cs | 5 ----- .../Difficulty/Preprocessing/TaikoDifficultyHitObject.cs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs index 45cc29c99e..8b126f85ce 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs @@ -16,10 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm foreach (var rhythmGroup in rhythmGroups) { foreach (var hitObject in rhythmGroup.HitObjects) - { hitObject.RhythmData.SameRhythmGroupedHitObjects = rhythmGroup; - hitObject.HitObjectInterval = rhythmGroup.HitObjectInterval; - } } var patternGroups = createSamePatternGroupedHitObjects(rhythmGroups); @@ -27,9 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm foreach (var patternGroup in patternGroups) { foreach (var hitObject in patternGroup.AllHitObjects) - { hitObject.RhythmData.SamePatternsGroupedHitObjects = patternGroup; - } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 5c5503c25d..489b36b259 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -43,11 +43,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing /// public readonly TaikoRhythmData RhythmData; - /// - /// The interval between this hit object and the surrounding hit objects in its rhythm group. - /// - 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. From 325483192a26f41d7019c4cf28c22fe91da1f1e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2025 15:13:04 +0900 Subject: [PATCH 21/23] Tidy up xmldoc and remove another unused field --- .../Preprocessing/TaikoDifficultyHitObject.cs | 52 ++++++++----------- .../Difficulty/TaikoDifficultyCalculator.cs | 1 - .../Difficulty/Utils/IHasInterval.cs | 2 +- 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 489b36b259..f407e13ff1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -6,6 +6,7 @@ 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; @@ -39,13 +40,14 @@ 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 TaikoRhythmData RhythmData; /// - /// 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. + /// Colour data used by and . + /// This is populated via . /// public readonly TaikoColourData ColourData; @@ -54,19 +56,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing /// 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. @@ -75,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing /// The position of this in the list. /// The control point info of the beatmap. /// The global slider velocity of the beatmap. - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, List objects, List centreHitObjects, List rimHitObjects, @@ -86,29 +80,26 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { noteDifficultyHitObjects = noteObjects; - // Create the Colour object, its properties should be filled in by TaikoDifficultyPreprocessor ColourData = new TaikoColourData(); - - // Create a Rhythm object, its properties are filled in by TaikoDifficultyHitObjectRhythm RhythmData = new TaikoRhythmData(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); } @@ -121,7 +112,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing // Calculate the slider velocity at the note's start time. double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, normalisedStartTime, clockRate); - CurrentSliderVelocity = currentSliderVelocity; EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity; } @@ -142,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/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index acd654f9b8..6b9986bd68 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -78,7 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty difficultyHitObjects.Add(new TaikoDifficultyHitObject( beatmap.HitObjects[i], beatmap.HitObjects[i - 1], - beatmap.HitObjects[i - 2], clockRate, difficultyHitObjects, centreObjects, diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs index 8f80bb6079..a42940180c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IHasInterval.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Utils public interface IHasInterval { /// - /// The interval between 2 objects start times. + /// The interval – ie delta time – between this object and a known previous object. /// double Interval { get; } } From 8447679db9f038b5ddfefbe7337d87ea38000c22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2025 15:41:31 +0900 Subject: [PATCH 22/23] Initial tidy-up pass on `IntervalGroupingUtils` --- .../TaikoRhythmDifficultyPreprocessor.cs | 17 +++----- .../Difficulty/Utils/IntervalGroupingUtils.cs | 41 ++++++++----------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs index 8b126f85ce..5bc0fdbc03 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Rhythm/TaikoRhythmDifficultyPreprocessor.cs @@ -2,6 +2,7 @@ // 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; @@ -31,13 +32,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm private static List createSameRhythmGroupedHitObjects(List hitObjects) { var rhythmGroups = new List(); - var groups = IntervalGroupingUtils.GroupByInterval(hitObjects); - foreach (var group in groups) - { - var previous = rhythmGroups.Count > 0 ? rhythmGroups[^1] : null; - rhythmGroups.Add(new SameRhythmHitObjectGrouping(previous, group)); - } + foreach (var grouped in IntervalGroupingUtils.GroupByInterval(hitObjects)) + rhythmGroups.Add(new SameRhythmHitObjectGrouping(rhythmGroups.LastOrDefault(), grouped)); return rhythmGroups; } @@ -45,13 +42,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm private static List createSamePatternGroupedHitObjects(List rhythmGroups) { var patternGroups = new List(); - var groups = IntervalGroupingUtils.GroupByInterval(rhythmGroups); - foreach (var group in groups) - { - var previous = patternGroups.Count > 0 ? patternGroups[^1] : null; - patternGroups.Add(new SamePatternsGroupedHitObjects(previous, group)); - } + foreach (var grouped in IntervalGroupingUtils.GroupByInterval(rhythmGroups)) + patternGroups.Add(new SamePatternsGroupedHitObjects(patternGroups.LastOrDefault(), grouped)); return patternGroups; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs index 3b6f5406b4..f04dec1c08 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs @@ -8,56 +8,51 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Utils { public static class IntervalGroupingUtils { - public static List> GroupByInterval(IReadOnlyList data, double marginOfError = 5) where T : IHasInterval + public static List> GroupByInterval(IReadOnlyList objects) where T : IHasInterval { var groups = new List>(); - if (data.Count == 0) - return groups; int i = 0; - - while (i < data.Count) - { - var group = createGroup(data, ref i, marginOfError); - groups.Add(group); - } + while (i < objects.Count) + groups.Add(createNextGroup(objects, ref i)); return groups; } - private static List createGroup(IReadOnlyList data, ref int i, double marginOfError) where T : IHasInterval + private static List createNextGroup(IReadOnlyList objects, ref int i) where T : IHasInterval { - var children = new List { data[i] }; + const double margin_of_error = 5; + + var groupedObjects = new List { objects[i] }; i++; - for (; i < data.Count - 1; i++) + for (; i < objects.Count - 1; i++) { - // An interval change occured, add the current data if the next interval is larger. - if (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, marginOfError)) + // An interval change occured, add the current object if the next interval is larger. + if (!Precision.AlmostEquals(objects[i].Interval, objects[i + 1].Interval, margin_of_error)) { - if (data[i + 1].Interval > data[i].Interval + marginOfError) + if (objects[i + 1].Interval > objects[i].Interval + margin_of_error) { - children.Add(data[i]); + groupedObjects.Add(objects[i]); i++; } - return children; + return groupedObjects; } // No interval change occurred - children.Add(data[i]); + groupedObjects.Add(objects[i]); } - // Check if the last two objects in the data form a "flat" rhythm pattern within the specified margin of error. + // 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 (data.Count > 2 && i < data.Count && - Precision.AlmostEquals(data[^1].Interval, data[^2].Interval, marginOfError)) + if (objects.Count > 2 && i < objects.Count && Precision.AlmostEquals(objects[^1].Interval, objects[^2].Interval, margin_of_error)) { - children.Add(data[i]); + groupedObjects.Add(objects[i]); i++; } - return children; + return groupedObjects; } } } From 40ea7ff2383248c4e3cdbd2c042cf692792f7bd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2025 18:48:48 +0900 Subject: [PATCH 23/23] Add better documentation for interval change code --- .../Difficulty/Utils/IntervalGroupingUtils.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs index f04dec1c08..7bd7aa7677 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Utils/IntervalGroupingUtils.cs @@ -28,9 +28,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Utils for (; i < objects.Count - 1; i++) { - // An interval change occured, add the current object if the next interval is larger. 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]);