From 7257aae7f26bd4c322d04e5aa739207773c5bfbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Aug 2021 18:00:57 +0900 Subject: [PATCH] Move samples to `LegacyControlPointInfo` --- .../Formats/LegacyBeatmapDecoderTest.cs | 5 +- .../NonVisual/ControlPointInfoTest.cs | 4 +- .../ControlPoints/ControlPointInfo.cs | 119 +++++++++++------- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 22 ++++ .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 +- osu.Game/Rulesets/Objects/HitObject.cs | 7 +- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 +- 7 files changed, 110 insertions(+), 63 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 12633ee8c9..24410c886f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -10,6 +10,7 @@ using osu.Game.Tests.Resources; using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; @@ -166,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = new LineBufferedReader(resStream)) { var beatmap = decoder.Decode(stream); - var controlPoints = beatmap.ControlPointInfo; + var controlPoints = (LegacyControlPointInfo)beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); @@ -240,7 +241,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = TestResources.OpenResource("overlapping-control-points.osu")) using (var stream = new LineBufferedReader(resStream)) { - var controlPoints = decoder.Decode(stream).ControlPointInfo; + var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo; Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4)); Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3)); diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 240ae4a90c..e1d6df814e 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestAddRedundantSample() { - var cpi = new ControlPointInfo(); + var cpi = new LegacyControlPointInfo(); cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point cpi.Add(1000, new SampleControlPoint()); // is redundant @@ -142,7 +142,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestRemoveGroupAlsoRemovedControlPoints() { - var cpi = new ControlPointInfo(); + var cpi = new LegacyControlPointInfo(); var group = cpi.GroupAt(1000, true); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 2d0fc17a7b..7cc4fa6dc4 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,6 +14,64 @@ using osu.Game.Utils; namespace osu.Game.Beatmaps.ControlPoints { + public class LegacyControlPointInfo : ControlPointInfo + { + /// + /// All sound points. + /// + [JsonProperty] + public IBindableList SamplePoints => samplePoints; + + private readonly BindableList samplePoints = new BindableList(); + + /// + /// Finds the sound control point that is active at . + /// + /// The time to find the sound control point at. + /// The sound control point. + [NotNull] + public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); + + public override void Clear() + { + base.Clear(); + samplePoints.Clear(); + } + + protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) + { + if (newPoint is SampleControlPoint _) + { + var existing = BinarySearch(SamplePoints, time); + return newPoint?.IsRedundant(existing) == true; + } + + return base.CheckAlreadyExisting(time, newPoint); + } + + protected override void GroupItemAdded(ControlPoint controlPoint) + { + if (controlPoint is SampleControlPoint typed) + { + samplePoints.Add(typed); + return; + } + + base.GroupItemAdded(controlPoint); + } + + protected override void GroupItemRemoved(ControlPoint controlPoint) + { + if (controlPoint is SampleControlPoint typed) + { + samplePoints.Remove(typed); + return; + } + + base.GroupItemRemoved(controlPoint); + } + } + [Serializable] public class ControlPointInfo : IDeepCloneable { @@ -41,14 +99,6 @@ namespace osu.Game.Beatmaps.ControlPoints private readonly SortedList difficultyPoints = new SortedList(Comparer.Default); - /// - /// All sound points. - /// - [JsonProperty] - public IBindableList SamplePoints => samplePoints; - - private readonly BindableList samplePoints = new BindableList(); - /// /// All effect points. /// @@ -69,7 +119,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the difficulty control point at. /// The difficulty control point. [NotNull] - public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT); + public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT); /// /// Finds the effect control point that is active at . @@ -77,15 +127,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the effect control point at. /// The effect control point. [NotNull] - public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT); - - /// - /// Finds the sound control point that is active at . - /// - /// The time to find the sound control point at. - /// The sound control point. - [NotNull] - public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); + public EffectControlPoint EffectPointAt(double time) => BinarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT); /// /// Finds the timing control point that is active at . @@ -93,7 +135,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the timing control point at. /// The timing control point. [NotNull] - public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); + public TimingControlPoint TimingPointAt(double time) => BinarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT); /// /// Finds the maximum BPM represented by any timing control point. @@ -112,12 +154,11 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Remove all s and return to a pristine state. /// - public void Clear() + public virtual void Clear() { groups.Clear(); timingPoints.Clear(); difficultyPoints.Clear(); - samplePoints.Clear(); effectPoints.Clear(); } @@ -129,7 +170,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether the control point was added. public bool Add(double time, ControlPoint controlPoint) { - if (checkAlreadyExisting(time, controlPoint)) + if (CheckAlreadyExisting(time, controlPoint)) return false; GroupAt(time, true).Add(controlPoint); @@ -147,8 +188,8 @@ namespace osu.Game.Beatmaps.ControlPoints if (addIfNotExisting) { - newGroup.ItemAdded += groupItemAdded; - newGroup.ItemRemoved += groupItemRemoved; + newGroup.ItemAdded += GroupItemAdded; + newGroup.ItemRemoved += GroupItemRemoved; groups.Insert(~i, newGroup); return newGroup; @@ -162,8 +203,8 @@ namespace osu.Game.Beatmaps.ControlPoints foreach (var item in group.ControlPoints.ToArray()) group.Remove(item); - group.ItemAdded -= groupItemAdded; - group.ItemRemoved -= groupItemRemoved; + group.ItemAdded -= GroupItemAdded; + group.ItemRemoved -= GroupItemRemoved; groups.Remove(group); } @@ -228,10 +269,10 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the control point at. /// The control point to use when is before any control points. /// The active control point at , or a fallback if none found. - private T binarySearchWithFallback(IReadOnlyList list, double time, T fallback) + protected T BinarySearchWithFallback(IReadOnlyList list, double time, T fallback) where T : ControlPoint { - return binarySearch(list, time) ?? fallback; + return BinarySearch(list, time) ?? fallback; } /// @@ -240,7 +281,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The list to search. /// The time to find the control point at. /// The active control point at . - private T binarySearch(IReadOnlyList list, double time) + protected virtual T BinarySearch(IReadOnlyList list, double time) where T : ControlPoint { if (list == null) @@ -280,7 +321,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the timing control point at. /// A point to be added. /// Whether the new point should be added. - private bool checkAlreadyExisting(double time, ControlPoint newPoint) + protected virtual bool CheckAlreadyExisting(double time, ControlPoint newPoint) { ControlPoint existing = null; @@ -288,17 +329,13 @@ namespace osu.Game.Beatmaps.ControlPoints { case TimingControlPoint _: // Timing points are a special case and need to be added regardless of fallback availability. - existing = binarySearch(TimingPoints, time); + existing = BinarySearch(TimingPoints, time); break; case EffectControlPoint _: existing = EffectPointAt(time); break; - case SampleControlPoint _: - existing = binarySearch(SamplePoints, time); - break; - case DifficultyControlPoint _: existing = DifficultyPointAt(time); break; @@ -307,7 +344,7 @@ namespace osu.Game.Beatmaps.ControlPoints return newPoint?.IsRedundant(existing) == true; } - private void groupItemAdded(ControlPoint controlPoint) + protected virtual void GroupItemAdded(ControlPoint controlPoint) { switch (controlPoint) { @@ -319,17 +356,13 @@ namespace osu.Game.Beatmaps.ControlPoints effectPoints.Add(typed); break; - case SampleControlPoint typed: - samplePoints.Add(typed); - break; - case DifficultyControlPoint typed: difficultyPoints.Add(typed); break; } } - private void groupItemRemoved(ControlPoint controlPoint) + protected virtual void GroupItemRemoved(ControlPoint controlPoint) { switch (controlPoint) { @@ -341,10 +374,6 @@ namespace osu.Game.Beatmaps.ControlPoints effectPoints.Remove(typed); break; - case SampleControlPoint typed: - samplePoints.Remove(typed); - break; - case DifficultyControlPoint typed: difficultyPoints.Remove(typed); break; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0ddc9e4c48..9bd76a9f2c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -44,6 +44,13 @@ namespace osu.Game.Beatmaps.Formats offset = FormatVersion < 5 ? 24 : 0; } + protected override Beatmap CreateTemplateObject() + { + var templateBeatmap = base.CreateTemplateObject(); + templateBeatmap.ControlPointInfo = new LegacyControlPointInfo(); + return templateBeatmap; + } + protected override void ParseStreamInto(LineBufferedReader stream, Beatmap beatmap) { this.beatmap = beatmap; @@ -394,8 +401,16 @@ namespace osu.Game.Beatmaps.Formats private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; + private readonly LegacyControlPointInfo controlPointInfo = new LegacyControlPointInfo(); + private void addControlPoint(double time, ControlPoint point, bool timingChange) { + if (point is SampleControlPoint) + { + controlPointInfo.Add(time, point); + return; + } + if (time != pendingControlPointsTime) flushPendingPoints(); @@ -430,8 +445,15 @@ namespace osu.Game.Beatmaps.Formats parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); var obj = parser.Parse(line); + if (obj != null) + { + // assign legacy control points directly to hitobject + //obj.SampleControlPoint = controlPointInfo.SamplePointAt(obj.StartTime); + obj.ApplyDefaults(controlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + beatmap.HitObjects.Add(obj); + } } private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 246dc991d5..a6c41a3cf6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -80,7 +80,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); - writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(beatmap.ControlPointInfo.SamplePointAt(double.MinValue).SampleBank)}")); + writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); @@ -187,7 +187,7 @@ namespace osu.Game.Beatmaps.Formats void outputControlPointEffectsAt(double time, bool isTimingPoint) { - var samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); + var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time); var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 422655502d..fd9f02604c 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -106,8 +106,11 @@ namespace osu.Game.Rulesets.Objects { ApplyDefaultsToSelf(controlPointInfo, difficulty); - // This is done here since ApplyDefaultsToSelf may be used to determine the end time - SampleControlPoint = controlPointInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); + if (controlPointInfo is LegacyControlPointInfo legacyInfo) + { + // This is done here since ApplyDefaultsToSelf may be used to determine the end time + SampleControlPoint = legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); + } nestedHitObjects.Clear(); diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index cc73af6349..be8de18502 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.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.Bindables; using osu.Framework.Graphics; @@ -42,15 +43,6 @@ namespace osu.Game.Screens.Edit.Timing } } - protected override SampleControlPoint CreatePoint() - { - var reference = Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); - - return new SampleControlPoint - { - SampleBank = reference.SampleBank, - SampleVolume = reference.SampleVolume, - }; - } + protected override SampleControlPoint CreatePoint() => new SampleControlPoint(); // TODO: remove } }