From 56c044c44ac6cc6ea460fef8883d99fb9a88aadc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 13:33:55 +0900 Subject: [PATCH] Move beat snapping to its own interface --- ...tSceneHitObjectComposerDistanceSnapping.cs | 9 +++- .../Visual/Editor/TestSceneComposeScreen.cs | 2 + .../Editor/TestSceneEditorComposeTimeline.cs | 4 +- .../Editor/TestSceneHitObjectComposer.cs | 1 + osu.Game/Rulesets/Edit/HitObjectComposer.cs | 26 +++-------- osu.Game/Rulesets/Edit/IBeatSnapProvider.cs | 30 ++++++++++++ .../Compose/Components/Timeline/Timeline.cs | 46 ++++--------------- osu.Game/Screens/Edit/Editor.cs | 15 +++++- osu.Game/Screens/Edit/EditorBeatmap.cs | 20 +++++++- 9 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IBeatSnapProvider.cs diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs index 2d336bd19c..e825df5a3f 100644 --- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; @@ -19,7 +20,13 @@ namespace osu.Game.Tests.Editor private TestHitObjectComposer composer; [Cached(typeof(EditorBeatmap))] - private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneHitObjectComposerDistanceSnapping() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap(), BeatDivisor); + } [SetUp] public void Setup() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs index 3562689482..a8830824c0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; @@ -14,6 +15,7 @@ namespace osu.Game.Tests.Visual.Editor public class TestSceneComposeScreen : EditorClockTestScene { [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 78cdeb3ac8..5b0408268f 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -38,9 +39,10 @@ namespace osu.Game.Tests.Visual.Editor { Beatmap.Value = new WaveformTestBeatmap(audio); - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor); Dependencies.Cache(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index c001c83877..e41c2427fb 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -66,6 +66,7 @@ namespace osu.Game.Tests.Visual.Editor Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); Dependencies.CacheAs(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); Child = new OsuHitObjectComposer(new OsuRuleset()); } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5eba31d149..50042d1e3b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Edit private IAdjustableClock adjustableClock { get; set; } [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } private IBeatmapProcessor beatmapProcessor; @@ -259,40 +259,26 @@ namespace osu.Game.Rulesets.Edit public override float GetBeatSnapDistanceAt(double referenceTime) { DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); - return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); + return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatSnapProvider.BeatDivisor); } public override float DurationToDistance(double referenceTime, double duration) { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime, beatSnapProvider.BeatDivisor); return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); } public override double DistanceToDuration(double referenceTime, float distance) { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime, beatSnapProvider.BeatDivisor); return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; } public override double GetSnappedDurationFromDistance(double referenceTime, float distance) - => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + => beatSnapProvider.SnapTime(referenceTime, DistanceToDuration(referenceTime, distance), beatSnapProvider.BeatDivisor); public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) - => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); - - /// - /// Snaps a duration to the closest beat of a timing point applicable at the reference time. - /// - /// The time of the timing point which resides in. - /// The duration to snap. - /// A value that represents snapped to the closest beat of the timing point. - private double beatSnap(double referenceTime, double duration) - { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - - // A 1ms offset prevents rounding errors due to minute variations in duration - return (int)((duration + 1) / beatLength) * beatLength; - } + => DurationToDistance(referenceTime, beatSnapProvider.SnapTime(referenceTime, DistanceToDuration(referenceTime, distance), beatSnapProvider.BeatDivisor)); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs new file mode 100644 index 0000000000..ed6e08d054 --- /dev/null +++ b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs @@ -0,0 +1,30 @@ +// 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.Edit +{ + public interface IBeatSnapProvider + { + /// + /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// + /// The time of the timing point which resides in. + /// The duration to snap. + /// The divisor to use for snapping purposes. + /// A value that represents snapped to the closest beat of the timing point. + double SnapTime(double referenceTime, double duration, int beatDivisor); + + /// + /// Get the most appropriate beat length at a given time. + /// + /// A reference time used for lookup. + /// The divisor to use for snapping purposes. + /// The most appropriate beat length. + double GetBeatLengthAtTime(double referenceTime, int beatDivisor); + + /// + /// Returns the current beat divisor. + /// + int BeatDivisor { get; } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 804c2f1e65..ec6f0bb923 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -10,7 +11,6 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osuTK; @@ -169,53 +169,25 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } + private EditorBeatmap beatmap { get; set; } [Resolved] - private EditorBeatmap beatmap { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) { var targetTime = (position.X / Content.DrawWidth) * track.Length; - return (position, beatSnap(targetTime, targetTime)); + return (position, beatSnapProvider.SnapTime(targetTime, targetTime, beatSnapProvider.BeatDivisor)); } - public float GetBeatSnapDistanceAt(double referenceTime) - { - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(referenceTime); - return (float)(100 * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); - } + public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); - public float DurationToDistance(double referenceTime, double duration) - { - double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); - } + public float DurationToDistance(double referenceTime, double duration) => throw new NotImplementedException(); - public double DistanceToDuration(double referenceTime, float distance) - { - double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; - } + public double DistanceToDuration(double referenceTime, float distance) => throw new NotImplementedException(); - public double GetSnappedDurationFromDistance(double referenceTime, float distance) - => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); - public float GetSnappedDistanceFromDistance(double referenceTime, float distance) - => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); - - /// - /// Snaps a duration to the closest beat of a timing point applicable at the reference time. - /// - /// The time of the timing point which resides in. - /// The duration to snap. - /// A value that represents snapped to the closest beat of the timing point. - private double beatSnap(double referenceTime, double duration) - { - double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - - // A 1ms offset prevents rounding errors due to minute variations in duration - return (int)((duration + 1) / beatLength) * beatLength; - } + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e212b429b9..26660c427c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -26,6 +26,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; @@ -34,7 +35,8 @@ using osu.Game.Users; namespace osu.Game.Screens.Edit { - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler + [Cached(typeof(IBeatSnapProvider))] + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IBeatSnapProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -77,11 +79,14 @@ namespace osu.Game.Screens.Edit clock.ChangeSource(sourceClock); playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor); dependencies.CacheAs(clock); dependencies.CacheAs(clock); + + // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); + dependencies.CacheAs(editorBeatmap); EditorMenuBar menuBar; @@ -345,5 +350,11 @@ namespace osu.Game.Screens.Edit saveBeatmap(); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } + + public double SnapTime(double referenceTime, double duration, int beatDivisor) => editorBeatmap.SnapTime(referenceTime, duration, beatDivisor); + + public double GetBeatLengthAtTime(double referenceTime, int beatDivisor) => editorBeatmap.GetBeatLengthAtTime(referenceTime, beatDivisor); + + public int BeatDivisor => beatDivisor.Value; } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 2ff7563a88..b7dd94b6c5 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -8,11 +8,12 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : IBeatmap + public class EditorBeatmap : IBeatmap, IBeatSnapProvider { /// /// Invoked when a is added to this . @@ -33,11 +34,14 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; + private readonly BindableBeatDivisor beatDivisor; + private readonly Dictionary> startTimeBindables = new Dictionary>(); - public EditorBeatmap(IBeatmap playableBeatmap) + public EditorBeatmap(IBeatmap playableBeatmap, BindableBeatDivisor beatDivisor = null) { PlayableBeatmap = playableBeatmap; + this.beatDivisor = beatDivisor; foreach (var obj in HitObjects) trackStartTime(obj); @@ -123,5 +127,17 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } + + public double SnapTime(double referenceTime, double duration, int beatDivisor) + { + double beatLength = GetBeatLengthAtTime(referenceTime, beatDivisor); + + // A 1ms offset prevents rounding errors due to minute variations in duration + return (int)((duration + 1) / beatLength) * beatLength; + } + + public double GetBeatLengthAtTime(double referenceTime, int beatDivisor) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; + + public int BeatDivisor => beatDivisor?.Value ?? 1; } }