-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,29 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; | ||
using osu.Game.Rulesets.Taiko.Objects; | ||
|
||
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators | ||
{ | ||
public static class PatternEvaluator | ||
{ | ||
public static double EvaluateDifficultyOf( | ||
TaikoDifficultyHitObject hitObject, | ||
TaikoPatternFields fields, | ||
double errorStandardDeviation, | ||
double hitWindowStandardDeviation) | ||
{ | ||
if (hitObject.BaseObject is not Hit) return 0; | ||
|
||
double errorAmplitude = fields.RhythmField.GetAmplitude(hitObject.StartTime, errorStandardDeviation); | ||
double hitAmplitude = fields.RhythmField.GetAmplitude(hitObject.StartTime, hitWindowStandardDeviation); | ||
|
||
// System.Console.WriteLine("errorAmplitude: " errorAmplitude ", hitAmplitude: " hitAmplitude); | ||
|
||
hitAmplitude = Math.Max(hitAmplitude, 0.1); | ||
return errorAmplitude / hitAmplitude; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,25 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
public class TaikoPatternFields | ||
Check failure on line 4 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/Data/TaikoPatternFields.cs GitHub Actions / Code Quality
|
||
{ | ||
/// <summary> | ||
/// Stores rhythm alignment amplitudes between all notes | ||
/// </summary> | ||
public TaikoTimeField RhythmField = new TaikoTimeField(); | ||
|
||
/// <summary> | ||
/// Stores rhythm alignment amplitudes between centre (don) notes | ||
/// </summary> | ||
public TaikoTimeField CentreField = new TaikoTimeField(); | ||
|
||
/// <summary> | ||
/// Stores rhythm alignment amplitudes between rim (katsu) notes | ||
/// </summary> | ||
public TaikoTimeField RimField = new TaikoTimeField(); | ||
|
||
/// <summary> | ||
/// Stores rhythm alignment amplitudes between colour changes | ||
/// </summary> | ||
public TaikoTimeField ColourChangeField = new TaikoTimeField(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,93 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
/// <summary> | ||
/// Stores amplitude points data by time. Getting amplitude at a specific time is defined as the sum of amplitudes | ||
/// within a range scaled by a normal distribution. | ||
/// </summary> | ||
public class TaikoTimeField | ||
Check failure on line 12 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/Data/TaikoTimeField.cs GitHub Actions / Code Quality
|
||
{ | ||
private readonly SortedList<double, double> amplitudesByTime = new SortedList<double, double>(); | ||
|
||
public void AddNode(double time, double amplitude) | ||
{ | ||
double resultAmplitude = amplitudesByTime.GetValueOrDefault(time, 0) amplitude; | ||
amplitudesByTime[time] = resultAmplitude; | ||
} | ||
|
||
private double bellCurve(double x, double standardDeviation) | ||
{ | ||
double scaledX = x / standardDeviation; | ||
double numerator = Math.Pow(Math.E, (-(scaledX * scaledX)) / 2); | ||
return numerator; | ||
} | ||
|
||
private int binarySearchGte(double startTime) | ||
{ | ||
int left = 0; | ||
int right = amplitudesByTime.Count - 1; | ||
int startIndex = -1; | ||
|
||
while (left <= right) | ||
{ | ||
int mid = left (right - left) / 2; | ||
if (amplitudesByTime.Keys[mid] == startTime) | ||
Check failure on line 38 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/Data/TaikoTimeField.cs GitHub Actions / Code Quality
|
||
{ | ||
startIndex = mid; | ||
break; | ||
} | ||
else if (amplitudesByTime.Keys[mid] < startTime) | ||
{ | ||
left = mid 1; | ||
} | ||
else | ||
{ | ||
startIndex = mid; | ||
right = mid - 1; | ||
} | ||
} | ||
|
||
return startIndex; | ||
} | ||
|
||
private List<(double time, double amplitude)> getNodesIn(double startTime, double endTime) | ||
{ | ||
List<(double, double)> withinRangeAmplitudes = new List<(double, double)>(); | ||
int startIndex = binarySearchGte(startTime); | ||
|
||
if (startIndex != -1) | ||
{ | ||
for (int i = startIndex; i < amplitudesByTime.Count && amplitudesByTime.Keys[i] <= endTime; i ) | ||
{ | ||
withinRangeAmplitudes.Add((amplitudesByTime.Keys[i], amplitudesByTime.Values[i])); | ||
} | ||
} | ||
|
||
return withinRangeAmplitudes; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the sum of amplitudes within a range scaled by a normal distribution. | ||
/// </summary> | ||
/// | ||
/// <param name="time">The center time point</param> | ||
/// <param name="standardDeviation"> | ||
/// Range of the normal distribution. Note that only amplitudes within 3 standard | ||
/// deviations are considered | ||
/// </param> | ||
/// <returns></returns> | ||
public double GetAmplitude(double time, double standardDeviation) | ||
{ | ||
List<(double time, double amplitude)> nodes = getNodesIn( | ||
time - standardDeviation * 3, time standardDeviation * 3); | ||
return nodes | ||
.Sum(x => | ||
{ | ||
return bellCurve(time - x.time, standardDeviation) * x.amplitude; | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,93 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using osu.Framework.Extensions.IEnumerableExtensions; | ||
using osu.Game.Rulesets.Taiko.Objects; | ||
|
||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Pattern | ||
{ | ||
public static class TaikoPatternDifficultyPreprocessor | ||
{ | ||
private const int harmonics = 8; | ||
|
||
private const double cycles_count = 4; | ||
|
||
private const double decay_base = Math.E; | ||
|
||
private static void createAlignmentPoints(double time, double interval, TaikoTimeField timeField) | ||
{ | ||
for (int i = 0; i < harmonics; i ) | ||
{ | ||
double harmonicInterval = interval * Math.Pow(2, i); | ||
double harmonicAmplitude = Math.Pow(0.5, i); | ||
|
||
for (int j = 0; j < cycles_count * (i 1); j ) | ||
{ | ||
double t = time harmonicInterval * j; | ||
double amplitude = harmonicAmplitude * Math.Pow(decay_base, j / (i 1)); | ||
Check failure on line 30 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/TaikoPatternDifficultyPreprocessor.cs GitHub Actions / Code Quality
|
||
timeField.AddNode(t, amplitude); | ||
} | ||
} | ||
} | ||
|
||
private static void createRhythmAlignmentPoints( | ||
IEnumerable<TaikoDifficultyHitObject> hitObjects, TaikoTimeField timeField) | ||
{ | ||
hitObjects | ||
.Where(hitObject => hitObject.DeltaTime > 0) | ||
.ForEach(hitObject => createAlignmentPoints(hitObject.StartTime, hitObject.DeltaTime, timeField)); | ||
} | ||
|
||
private static void createColourAlignmentPoints( | ||
IEnumerable<TaikoDifficultyHitObject> hitObject, HitType type, TaikoTimeField timeField) | ||
{ | ||
hitObject | ||
.Where(hitObject => (hitObject.BaseObject as Hit)?.Type == type) | ||
Check failure on line 48 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/TaikoPatternDifficultyPreprocessor.cs GitHub Actions / Code Quality
|
||
.SelectPair((previous, current) => | ||
(interval: current.StartTime - previous.StartTime, hitObject: current)) | ||
.ForEach(x => createAlignmentPoints(x.hitObject.StartTime, x.interval, timeField)); | ||
} | ||
|
||
|
||
private static void createCentreAlignmentPoints( | ||
Check failure on line 55 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/TaikoPatternDifficultyPreprocessor.cs GitHub Actions / Code Quality
|
||
IEnumerable<TaikoDifficultyHitObject> hitObject, TaikoPatternFields fields | ||
) => createColourAlignmentPoints(hitObject, HitType.Centre, fields.CentreField); | ||
|
||
private static void createRimAlignmentPoints( | ||
IEnumerable<TaikoDifficultyHitObject> hitObject, TaikoPatternFields fields | ||
) => createColourAlignmentPoints(hitObject, HitType.Rim, fields.RimField); | ||
|
||
private static void createColourChangeAlignmentPoints( | ||
IEnumerable<TaikoDifficultyHitObject> hitObject, TaikoPatternFields fields | ||
) | ||
{ | ||
hitObject | ||
.Where(hitObject => hitObject.BaseObject is Hit) | ||
Check failure on line 68 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/TaikoPatternDifficultyPreprocessor.cs GitHub Actions / Code Quality
Check failure on line 68 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/TaikoPatternDifficultyPreprocessor.cs GitHub Actions / Code Quality
|
||
.SelectPair((previous, current) => | ||
{ | ||
var previousHit = (Hit)previous.BaseObject; | ||
var currentHit = (Hit)current.BaseObject; | ||
return previousHit.Type == currentHit.Type ? current : null; | ||
}) | ||
.OfType<TaikoDifficultyHitObject>() | ||
.SelectPair( | ||
(previous, current) => (interval: current.StartTime - previous.StartTime, hitObject: current)) | ||
.ForEach(x => createAlignmentPoints(x.hitObject.StartTime, x.interval, fields.ColourChangeField)); | ||
} | ||
|
||
public static TaikoPatternFields ComputeFields(IEnumerable<TaikoDifficultyHitObject> hitObjects) | ||
{ | ||
TaikoPatternFields fields = new TaikoPatternFields(); | ||
|
||
createRhythmAlignmentPoints(hitObjects, fields.RhythmField); | ||
createCentreAlignmentPoints(hitObjects, fields); | ||
createRimAlignmentPoints(hitObjects, fields); | ||
createColourChangeAlignmentPoints(hitObjects, fields); | ||
|
||
return fields; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,17 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
public static class CollectionUtils | ||
Check failure on line 8 in osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/Pattern/Utils/CollectionUtils.cs GitHub Actions / Code Quality
|
||
{ | ||
public static IEnumerable<TargetType> SelectPair<SourceType, TargetType>( | ||
this IEnumerable<SourceType> source, Func<SourceType, SourceType, TargetType> operation) | ||
{ | ||
return source | ||
.Zip(source.Skip(1), source.SkipLast(1)) | ||
.Select(x => operation(x.First, x.Second)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,54 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
|
||
using System; | ||
using osu.Game.Beatmaps; | ||
using osu.Game.Rulesets.Difficulty.Preprocessing; | ||
using osu.Game.Rulesets.Difficulty.Skills; | ||
using osu.Game.Rulesets.Mods; | ||
using osu.Game.Rulesets.Scoring; | ||
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators; | ||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; | ||
using osu.Game.Rulesets.Taiko.Scoring; | ||
|
||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills | ||
{ | ||
public class Pattern : StrainDecaySkill | ||
{ | ||
protected override double SkillMultiplier => 1; | ||
|
||
protected override double StrainDecayBase => 0; | ||
|
||
private TaikoPatternFields? fields; | ||
|
||
private const double rhythm_error_standard_deviation = 1000.0; | ||
|
||
private double? hitWindowStandardDeviation; | ||
|
||
public Pattern(Mod[] mods) : base(mods) { } | ||
|
||
public void Initialize(IBeatmap beatmap, TaikoPatternFields fields) | ||
{ | ||
this.fields = fields; | ||
|
||
HitWindows hitWindows = new TaikoHitWindows(); | ||
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); | ||
hitWindowStandardDeviation = hitWindows.WindowFor(HitResult.Great) / 4; | ||
} | ||
|
||
protected override double StrainValueOf(DifficultyHitObject current) | ||
{ | ||
if (fields == null || hitWindowStandardDeviation == null) | ||
{ | ||
throw new InvalidOperationException("Pattern skill class has not been initialized. Please call Initialize first."); | ||
} | ||
|
||
return PatternEvaluator.EvaluateDifficultyOf( | ||
(TaikoDifficultyHitObject)current, | ||
fields, | ||
rhythm_error_standard_deviation, | ||
(double)hitWindowStandardDeviation); | ||
} | ||
} | ||
} |