// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints { [Serializable] public class ControlPointInfo { /// /// All timing points. /// [JsonProperty] public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); /// /// All difficulty points. /// [JsonProperty] public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); /// /// All sound points. /// [JsonProperty] public SortedList SamplePoints { get; private set; } = new SortedList(Comparer.Default); /// /// All effect points. /// [JsonProperty] public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); /// /// Finds the difficulty control point that is active at . /// /// The time to find the difficulty control point at. /// The difficulty control point. public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time); /// /// Finds the effect control point that is active at . /// /// The time to find the effect control point at. /// The effect control point. public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time); /// /// Finds the sound control point that is active at . /// /// The time to find the sound control point at. /// The sound control point. public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault()); /// /// Finds the timing control point that is active at . /// /// The time to find the timing control point at. /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); /// /// Finds the maximum BPM represented by any timing control point. /// [JsonIgnore] public double BPMMaximum => 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; /// /// Finds the minimum BPM represented by any timing control point. /// [JsonIgnore] public double BPMMinimum => 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; /// /// Finds the mode BPM (most common BPM) represented by the control points. /// [JsonIgnore] public double BPMMode => 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; /// /// Binary searches one of the control point lists to find the active control point at . /// /// The list to search. /// The time to find the control point at. /// The control point to use when is before any control points. If null, a new control point will be constructed. /// The active control point at . private T binarySearch(SortedList list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) throw new ArgumentNullException(nameof(list)); if (list.Count == 0) return new T(); if (time < list[0].Time) return prePoint ?? new T(); int index = list.BinarySearch(new T { Time = time }); // Check if we've found an exact match (t == time) if (index >= 0) return list[index]; index = ~index; // BinarySearch will return the index of the first element _greater_ than the search // This is the inactive point - the active point is the one before it (index - 1) return list[index - 1]; } } }