From b4dc39412750b350a33f94fdf61c9016289892d3 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 1 Aug 2025 16:01:28 +0100 Subject: [PATCH 1/4] add check for inconsistent timing --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../CheckInconsistentTimingControlPoints.cs | 162 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 868835342a..c0fc400a53 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Edit // Timing new CheckPreviewTime(), + new CheckInconsistentTimingControlPoints(), // Events new CheckBreaks(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs b/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs new file mode 100644 index 0000000000..b8694c52cc --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs @@ -0,0 +1,162 @@ +// 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 System.Linq; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckInconsistentTimingControlPoints : ICheck + { + // Small tolerance for floating point comparison + private const double timing_tolerance = 0.01; + + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Timing, "Inconsistent timing control points"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateMissingTimingPoint(this), + new IssueTemplateExtraTimingPoint(this), + new IssueTemplateMissingTimingPointMinor(this), + new IssueTemplateInconsistentMeter(this), + new IssueTemplateInconsistentBPM(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var difficulties = context.BeatmapsetDifficulties; + + if (difficulties.Count <= 1) + yield break; + + // Use the current difficulty as reference + var referenceBeatmap = context.Beatmap; + var referenceTimingPoints = referenceBeatmap.ControlPointInfo.TimingPoints; + + foreach (var beatmap in difficulties) + { + if (beatmap == referenceBeatmap) + continue; + + var timingPoints = beatmap.ControlPointInfo.TimingPoints; + + // Check each timing point in the reference against this difficulty + foreach (var referencePoint in referenceTimingPoints) + { + var matchingPoint = findMatchingTimingPoint(timingPoints, referencePoint.Time); + var exactMatchingPoint = findExactMatchingTimingPoint(timingPoints, referencePoint.Time); + + if (matchingPoint == null) + { + yield return new IssueTemplateMissingTimingPoint(this).Create(referencePoint.Time, beatmap.BeatmapInfo.DifficultyName); + } + else + { + // Check for meter signature inconsistency + if (!referencePoint.TimeSignature.Equals(matchingPoint.TimeSignature)) + { + yield return new IssueTemplateInconsistentMeter(this).Create(referencePoint.Time, beatmap.BeatmapInfo.DifficultyName); + } + + // Check for BPM inconsistency + if (Math.Abs(referencePoint.BeatLength - matchingPoint.BeatLength) > timing_tolerance) + { + yield return new IssueTemplateInconsistentBPM(this).Create(referencePoint.Time, beatmap.BeatmapInfo.DifficultyName); + } + + // Check for exact timing match (decimal precision) + if (exactMatchingPoint == null) + { + yield return new IssueTemplateMissingTimingPointMinor(this).Create(referencePoint.Time, beatmap.BeatmapInfo.DifficultyName); + } + } + } + + // Check timing points in this difficulty that aren't in the reference + foreach (var timingPoint in timingPoints) + { + var matchingReferencePoint = findMatchingTimingPoint(referenceTimingPoints, timingPoint.Time); + var exactMatchingReferencePoint = findExactMatchingTimingPoint(referenceTimingPoints, timingPoint.Time); + + if (matchingReferencePoint == null) + { + yield return new IssueTemplateExtraTimingPoint(this).Create(timingPoint.Time, beatmap.BeatmapInfo.DifficultyName); + } + else if (exactMatchingReferencePoint == null) + { + yield return new IssueTemplateMissingTimingPointMinor(this).Create(timingPoint.Time, beatmap.BeatmapInfo.DifficultyName); + } + } + } + } + + private static TimingControlPoint? findMatchingTimingPoint(IEnumerable timingPoints, double time) + { + return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, Math.Round(time), 1.0)); + } + + private static TimingControlPoint? findExactMatchingTimingPoint(IEnumerable timingPoints, double time) + { + return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, time, timing_tolerance)); + } + + public class IssueTemplateMissingTimingPoint : IssueTemplate + { + public IssueTemplateMissingTimingPoint(ICheck check) + : base(check, IssueType.Problem, "Missing timing control point in {0}.") + { + } + + public Issue Create(double time, string difficultyName) + => new Issue(time, this, difficultyName); + } + + public class IssueTemplateExtraTimingPoint : IssueTemplate + { + public IssueTemplateExtraTimingPoint(ICheck check) + : base(check, IssueType.Problem, "Extra timing control point in {0}.") + { + } + + public Issue Create(double time, string difficultyName) + => new Issue(time, this, difficultyName); + } + + public class IssueTemplateMissingTimingPointMinor : IssueTemplate + { + public IssueTemplateMissingTimingPointMinor(ICheck check) + : base(check, IssueType.Negligible, "Timing control point has decimally different offset in {0}.") + { + } + + public Issue Create(double time, string difficultyName) + => new Issue(time, this, difficultyName); + } + + public class IssueTemplateInconsistentMeter : IssueTemplate + { + public IssueTemplateInconsistentMeter(ICheck check) + : base(check, IssueType.Problem, "Inconsistent time signature in {0}.") + { + } + + public Issue Create(double time, string difficultyName) + => new Issue(time, this, difficultyName); + } + + public class IssueTemplateInconsistentBPM : IssueTemplate + { + public IssueTemplateInconsistentBPM(ICheck check) + : base(check, IssueType.Problem, "Inconsistent BPM in {0}.") + { + } + + public Issue Create(double time, string difficultyName) + => new Issue(time, this, difficultyName); + } + } +} From 3a44e1d5be324f70db28e12ca9c6bad4e2f9d79d Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 1 Aug 2025 16:03:05 +0100 Subject: [PATCH 2/4] add tests --- ...heckInconsistentTimingControlPointsTest.cs | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckInconsistentTimingControlPointsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckInconsistentTimingControlPointsTest.cs b/osu.Game.Tests/Editing/Checks/CheckInconsistentTimingControlPointsTest.cs new file mode 100644 index 0000000000..899a59a24f --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckInconsistentTimingControlPointsTest.cs @@ -0,0 +1,256 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckInconsistentTimingControlPointsTest + { + private CheckInconsistentTimingControlPoints check = null!; + + [SetUp] + public void Setup() + { + check = new CheckInconsistentTimingControlPoints(); + } + + [Test] + public void TestConsistentTiming() + { + var beatmaps = createBeatmapSetWithTiming( + new[] { 1000.0, 2000.0 }, // Timing at 1000ms and 2000ms + new[] { 1000.0, 2000.0 } // Same timing + ); + + var context = createContext(beatmaps[0], beatmaps); + Assert.That(check.Run(context), Is.Empty); + } + + [Test] + public void TestMissingTimingPoint() + { + var beatmaps = createBeatmapSetWithTiming( + new[] { 1000.0, 2000.0 }, // Reference has timing at 1000ms and 2000ms + new[] { 1000.0 } // Second difficulty missing timing at 2000ms + ); + + var context = createContext(beatmaps[0], beatmaps); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckInconsistentTimingControlPoints.IssueTemplateMissingTimingPoint)); + } + + [Test] + public void TestInconsistentBPM() + { + var beatmaps = createBeatmapSetWithBPM( + new[] { (1000.0, 500.0) }, // Reference: 120 BPM (500ms beat length) + new[] { (1000.0, 600.0) } // Second: 100 BPM (600ms beat length) + ); + + var context = createContext(beatmaps[0], beatmaps); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckInconsistentTimingControlPoints.IssueTemplateInconsistentBPM)); + } + + [Test] + public void TestInconsistentMeter() + { + var beatmaps = createBeatmapSetWithMeter( + new[] { (1000.0, TimeSignature.SimpleQuadruple) }, // Reference: 4/4 + new[] { (1000.0, TimeSignature.SimpleTriple) } // Second: 3/4 + ); + + var context = createContext(beatmaps[0], beatmaps); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckInconsistentTimingControlPoints.IssueTemplateInconsistentMeter)); + } + + [Test] + public void TestDecimalOffset() + { + var beatmaps = createBeatmapSetWithTiming( + new[] { 1000.0 }, // Reference at exactly 1000ms + new[] { 1000.5 } // Second at 1000.5ms (decimal difference) + ); + + var context = createContext(beatmaps[0], beatmaps); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(2)); + Assert.That(issues.All(issue => issue.Template is CheckInconsistentTimingControlPoints.IssueTemplateMissingTimingPointMinor)); + } + + [Test] + public void TestSingleDifficulty() + { + var beatmaps = createBeatmapSetWithTiming( + new[] { 1000.0, 2000.0 } // Only one difficulty + ); + + var context = createContext(beatmaps[0], beatmaps); + Assert.That(check.Run(context), Is.Empty); + } + + [Test] + public void TestExtraTimingPoint() + { + var beatmaps = createBeatmapSetWithTiming( + new[] { 1000.0 }, // Reference has timing at 1000ms + new[] { 1000.0, 2000.0 } // Second has additional timing at 2000ms + ); + + var context = createContext(beatmaps[0], beatmaps); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckInconsistentTimingControlPoints.IssueTemplateExtraTimingPoint)); + } + + private IBeatmap[] createBeatmapSetWithTiming(params double[][] timingPoints) + { + var beatmapSet = new BeatmapSetInfo(); + var beatmaps = new IBeatmap[timingPoints.Length]; + + for (int i = 0; i < timingPoints.Length; i++) + { + beatmaps[i] = createBeatmapWithTiming(timingPoints[i], $"Difficulty {i + 1}"); + beatmaps[i].BeatmapInfo.BeatmapSet = beatmapSet; + } + + foreach (var beatmap in beatmaps) + beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); + + return beatmaps; + } + + private IBeatmap[] createBeatmapSetWithBPM(params (double time, double beatLength)[][] timingData) + { + var beatmapSet = new BeatmapSetInfo(); + var beatmaps = new IBeatmap[timingData.Length]; + + for (int i = 0; i < timingData.Length; i++) + { + beatmaps[i] = createBeatmapWithBPM(timingData[i], $"Difficulty {i + 1}"); + beatmaps[i].BeatmapInfo.BeatmapSet = beatmapSet; + } + + foreach (var beatmap in beatmaps) + beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); + + return beatmaps; + } + + private IBeatmap[] createBeatmapSetWithMeter(params (double time, TimeSignature meter)[][] timingData) + { + var beatmapSet = new BeatmapSetInfo(); + var beatmaps = new IBeatmap[timingData.Length]; + + for (int i = 0; i < timingData.Length; i++) + { + beatmaps[i] = createBeatmapWithMeter(timingData[i], $"Difficulty {i + 1}"); + beatmaps[i].BeatmapInfo.BeatmapSet = beatmapSet; + } + + foreach (var beatmap in beatmaps) + beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); + + return beatmaps; + } + + private IBeatmap createBeatmapWithTiming(double[] timingPoints, string difficultyName) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + DifficultyName = difficultyName, + Metadata = new BeatmapMetadata() + }, + ControlPointInfo = new ControlPointInfo() + }; + + foreach (double time in timingPoints) + { + beatmap.ControlPointInfo.Add(time, new TimingControlPoint + { + BeatLength = 500 // 120 BPM + }); + } + + return beatmap; + } + + private IBeatmap createBeatmapWithBPM((double time, double beatLength)[] timingData, string difficultyName) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + DifficultyName = difficultyName, + Metadata = new BeatmapMetadata() + }, + ControlPointInfo = new ControlPointInfo() + }; + + foreach ((double time, double beatLength) in timingData) + { + beatmap.ControlPointInfo.Add(time, new TimingControlPoint + { + BeatLength = beatLength + }); + } + + return beatmap; + } + + private IBeatmap createBeatmapWithMeter((double time, TimeSignature meter)[] timingData, string difficultyName) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + DifficultyName = difficultyName, + Metadata = new BeatmapMetadata() + }, + ControlPointInfo = new ControlPointInfo() + }; + + foreach ((double time, var meter) in timingData) + { + beatmap.ControlPointInfo.Add(time, new TimingControlPoint + { + BeatLength = 500, // 120 BPM + TimeSignature = meter + }); + } + + return beatmap; + } + + private BeatmapVerifierContext createContext(IBeatmap currentBeatmap, IBeatmap[] allDifficulties) + { + return new BeatmapVerifierContext( + currentBeatmap, + new TestWorkingBeatmap(currentBeatmap), + DifficultyRating.ExpertPlus, + beatmapInfo => allDifficulties.FirstOrDefault(b => b.BeatmapInfo.Equals(beatmapInfo)) + ); + } + } +} From 0fcef7b0ee509869c175a677a28585f277cd4965 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 1 Aug 2025 18:37:54 +0100 Subject: [PATCH 3/4] move methods to `TimingCheckUtils` so they can be reused for future timing-related checks --- .../CheckInconsistentTimingControlPoints.cs | 26 +++---------- .../Checks/Components/TimingCheckUtils.cs | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs diff --git a/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs b/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs index b8694c52cc..bbed49d7ee 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs @@ -3,18 +3,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using osu.Framework.Utils; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks { public class CheckInconsistentTimingControlPoints : ICheck { - // Small tolerance for floating point comparison - private const double timing_tolerance = 0.01; - public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Timing, "Inconsistent timing control points"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -47,8 +41,8 @@ namespace osu.Game.Rulesets.Edit.Checks // Check each timing point in the reference against this difficulty foreach (var referencePoint in referenceTimingPoints) { - var matchingPoint = findMatchingTimingPoint(timingPoints, referencePoint.Time); - var exactMatchingPoint = findExactMatchingTimingPoint(timingPoints, referencePoint.Time); + var matchingPoint = TimingCheckUtils.FindMatchingTimingPoint(timingPoints, referencePoint.Time); + var exactMatchingPoint = TimingCheckUtils.FindExactMatchingTimingPoint(timingPoints, referencePoint.Time); if (matchingPoint == null) { @@ -63,7 +57,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // Check for BPM inconsistency - if (Math.Abs(referencePoint.BeatLength - matchingPoint.BeatLength) > timing_tolerance) + if (Math.Abs(referencePoint.BeatLength - matchingPoint.BeatLength) > TimingCheckUtils.TIMING_TOLERANCE) { yield return new IssueTemplateInconsistentBPM(this).Create(referencePoint.Time, beatmap.BeatmapInfo.DifficultyName); } @@ -79,8 +73,8 @@ namespace osu.Game.Rulesets.Edit.Checks // Check timing points in this difficulty that aren't in the reference foreach (var timingPoint in timingPoints) { - var matchingReferencePoint = findMatchingTimingPoint(referenceTimingPoints, timingPoint.Time); - var exactMatchingReferencePoint = findExactMatchingTimingPoint(referenceTimingPoints, timingPoint.Time); + var matchingReferencePoint = TimingCheckUtils.FindMatchingTimingPoint(referenceTimingPoints, timingPoint.Time); + var exactMatchingReferencePoint = TimingCheckUtils.FindExactMatchingTimingPoint(referenceTimingPoints, timingPoint.Time); if (matchingReferencePoint == null) { @@ -94,16 +88,6 @@ namespace osu.Game.Rulesets.Edit.Checks } } - private static TimingControlPoint? findMatchingTimingPoint(IEnumerable timingPoints, double time) - { - return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, Math.Round(time), 1.0)); - } - - private static TimingControlPoint? findExactMatchingTimingPoint(IEnumerable timingPoints, double time) - { - return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, time, timing_tolerance)); - } - public class IssueTemplateMissingTimingPoint : IssueTemplate { public IssueTemplateMissingTimingPoint(ICheck check) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs b/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs new file mode 100644 index 0000000000..1ddbeb31d6 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs @@ -0,0 +1,39 @@ +// 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 System.Linq; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Edit.Checks.Components +{ + public static class TimingCheckUtils + { + // Small tolerance for floating point comparison + public const double TIMING_TOLERANCE = 0.01; + + /// + /// Finds a timing control point that starts at approximately the same time (within 1ms after rounding). + /// + /// The collection of timing points to search. + /// The time to match against. + /// The matching timing control point, or null if none found. + public static TimingControlPoint? FindMatchingTimingPoint(IEnumerable timingPoints, double time) + { + return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, Math.Round(time), 1.0)); + } + + /// + /// Finds a timing control point that starts at precisely the same time (within timing tolerance). + /// + /// The collection of timing points to search. + /// The time to match against. + /// The exact matching timing control point, or null if none found. + public static TimingControlPoint? FindExactMatchingTimingPoint(IEnumerable timingPoints, double time) + { + return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, time, TIMING_TOLERANCE)); + } + } +} From a7f0ae09bbad5a2677fbfe4f631de250f7d80cea Mon Sep 17 00:00:00 2001 From: Hivie Date: Mon, 4 Aug 2025 15:21:43 +0100 Subject: [PATCH 4/4] apply review - improve timing tolerance const name and documentation - simplify matching logic in `FindMatchingTimingPoint` --- .../Edit/Checks/CheckInconsistentTimingControlPoints.cs | 2 +- .../Rulesets/Edit/Checks/Components/TimingCheckUtils.cs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs b/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs index bbed49d7ee..8ed802e618 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckInconsistentTimingControlPoints.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // Check for BPM inconsistency - if (Math.Abs(referencePoint.BeatLength - matchingPoint.BeatLength) > TimingCheckUtils.TIMING_TOLERANCE) + if (Math.Abs(referencePoint.BeatLength - matchingPoint.BeatLength) > TimingCheckUtils.TIME_OFFSET_TOLERANCE_MS) { yield return new IssueTemplateInconsistentBPM(this).Create(referencePoint.Time, beatmap.BeatmapInfo.DifficultyName); } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs b/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs index 1ddbeb31d6..f56f27813d 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/TimingCheckUtils.cs @@ -1,7 +1,6 @@ // 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 System.Linq; using osu.Framework.Utils; @@ -11,8 +10,8 @@ namespace osu.Game.Rulesets.Edit.Checks.Components { public static class TimingCheckUtils { - // Small tolerance for floating point comparison - public const double TIMING_TOLERANCE = 0.01; + // Tolerance for exact time offset matching (in milliseconds) + public const double TIME_OFFSET_TOLERANCE_MS = 0.01; /// /// Finds a timing control point that starts at approximately the same time (within 1ms after rounding). @@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// The matching timing control point, or null if none found. public static TimingControlPoint? FindMatchingTimingPoint(IEnumerable timingPoints, double time) { - return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, Math.Round(time), 1.0)); + return timingPoints.FirstOrDefault(tp => (int)tp.Time == (int)time); } /// @@ -33,7 +32,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// The exact matching timing control point, or null if none found. public static TimingControlPoint? FindExactMatchingTimingPoint(IEnumerable timingPoints, double time) { - return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, time, TIMING_TOLERANCE)); + return timingPoints.FirstOrDefault(tp => Precision.AlmostEquals(tp.Time, time, TIME_OFFSET_TOLERANCE_MS)); } } }