1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 07:32:55 +08:00

Move snapping responsibility to IBeatmap

Seems `EditorBeatmap` already implements a different kind of `SnapTime` from `IBeatSnapProvider`, so method names here aren't great.

This is very similar to what https://github.com/ppy/osu/pull/12558 is doing, so may need to do some duplicate resolution later, especially surrounding `ClosestBeatSnapDivisor`.

Worth noting that this change makes 1/7, 1/5, etc unsupported for now, as we now rely on `BindableBeatDivisor.VALID_DIVISORS`.
This commit is contained in:
Naxess 2021-04-26 05:07:24 +02:00
parent 9178aa1d7d
commit 049e42fa85
5 changed files with 68 additions and 29 deletions

View File

@ -9,6 +9,7 @@ using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using Newtonsoft.Json;
using osu.Game.IO.Serialization.Converters;
using osu.Game.Screens.Edit;
namespace osu.Game.Beatmaps
{
@ -74,6 +75,31 @@ namespace osu.Game.Beatmaps
return mostCommon.beatLength;
}
public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null)
{
var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time);
var beatLength = timingPoint.BeatLength / beatDivisor;
var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero);
return (int)(timingPoint.Time + beatLengths * beatLength);
}
public int SnapTimeAnyDivisor(double time, double? referenceTime = null)
{
return SnapTimeForDivisor(time, ClosestBeatSnapDivisor(time, referenceTime), referenceTime);
}
public int ClosestBeatSnapDivisor(double time, double? referenceTime = null)
{
double getUnsnap(int divisor) => Math.Abs(time - SnapTimeForDivisor(time, divisor, referenceTime));
int[] divisors = BindableBeatDivisor.VALID_DIVISORS;
double smallestUnsnap = divisors.Min(getUnsnap);
int closestDivisor = divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap);
return closestDivisor;
}
IBeatmap IBeatmap.Clone() => Clone();
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();

View File

@ -51,6 +51,28 @@ namespace osu.Game.Beatmaps
/// </summary>
double GetMostCommonBeatLength();
/// <summary>
/// Returns the time on the given beat divisor closest to the given time.
/// </summary>
/// <param name="time">The time to find the closest snapped time to.</param>
/// <param name="beatDivisor">The beat divisor to snap to.</param>
/// <param name="referenceTime">The time at which the timing point is retrieved, by default same as time.</param>
int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null);
/// <summary>
/// Returns the time on any valid beat divisor closest to the given time.
/// </summary>
/// <param name="time">The time to find the closest snapped time to.</param>
/// <param name="referenceTime">The time at which the timing point is retrieved, by default same as time.</param>
int SnapTimeAnyDivisor(double time, double? referenceTime = null);
/// <summary>
/// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned.
/// </summary>
/// <param name="time">The time to find the closest beat snap divisor to.</param>
/// <param name="referenceTime">The time at which the timing point is retrieved, by default same as time.</param>
int ClosestBeatSnapDivisor(double time, double? referenceTime = null);
/// <summary>
/// Creates a shallow-clone of this beatmap and returns it.
/// </summary>

View File

@ -3,9 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Checks.Components;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@ -16,8 +14,6 @@ namespace osu.Game.Rulesets.Edit.Checks
{
private const double unsnap_ms_threshold = 2;
private static readonly int[] greatest_common_divisors = { 16, 12, 9, 7, 5 };
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
@ -30,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Checks
{
foreach (var hitobject in playableBeatmap.HitObjects)
{
double startUnsnap = hitobject.StartTime - closestSnapTime(playableBeatmap, hitobject.StartTime);
double startUnsnap = hitobject.StartTime - playableBeatmap.SnapTimeAnyDivisor(hitobject.StartTime);
string startPostfix = hitobject is IHasDuration ? "start" : "";
foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix))
yield return issue;
@ -41,7 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks
{
double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1);
double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1);
double repeatUnsnap = repeatTime - closestSnapTime(playableBeatmap, repeatTime);
double repeatUnsnap = repeatTime - playableBeatmap.SnapTimeAnyDivisor(repeatTime);
foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat"))
yield return issue;
}
@ -49,7 +45,7 @@ namespace osu.Game.Rulesets.Edit.Checks
if (hitobject is IHasDuration hasDuration)
{
double endUnsnap = hasDuration.EndTime - closestSnapTime(playableBeatmap, hasDuration.EndTime);
double endUnsnap = hasDuration.EndTime - playableBeatmap.SnapTimeAnyDivisor(hasDuration.EndTime);
foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end"))
yield return issue;
}
@ -66,23 +62,6 @@ namespace osu.Game.Rulesets.Edit.Checks
// We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works.
}
private int closestSnapTime(IBeatmap playableBeatmap, double time)
{
var timingPoint = playableBeatmap.ControlPointInfo.TimingPointAt(time);
double smallestUnsnap = greatest_common_divisors.Select(divisor => Math.Abs(time - snapTime(timingPoint, time, divisor))).Min();
return (int)Math.Round(time + smallestUnsnap);
}
private int snapTime(TimingControlPoint timingPoint, double time, int beatDivisor)
{
double beatLength = timingPoint.BeatLength / beatDivisor;
int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero);
// Casting to int matches the editor in both stable and lazer.
return (int)(timingPoint.Time + beatLengths * beatLength);
}
public abstract class IssueTemplateUnsnap : IssueTemplate
{
protected IssueTemplateUnsnap(ICheck check, IssueType type)

View File

@ -301,14 +301,17 @@ namespace osu.Game.Screens.Edit
return list.Count - 1;
}
public double SnapTime(double time, double? referenceTime)
public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null)
{
var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time);
var beatLength = timingPoint.BeatLength / BeatDivisor;
return timingPoint.Time + (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero) * beatLength;
return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime);
}
public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime);
public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime);
public double SnapTime(double time, double? referenceTime) => SnapTimeForDivisor(time, BeatDivisor, referenceTime);
public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor;
public int BeatDivisor => beatDivisor?.Value ?? 1;

View File

@ -45,6 +45,15 @@ namespace osu.Game.Screens.Play
public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength();
public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null)
{
return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime);
}
public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime);
public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime);
public IBeatmap Clone() => PlayableBeatmap.Clone();
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();