2018-01-05 19:21:19 +08:00
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
2017-05-23 14:29:38 +08:00
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2017-11-08 06:08:24 +08:00
using System ;
2017-05-23 12:55:18 +08:00
using System.Collections.Generic ;
using System.Linq ;
2017-12-05 23:37:37 +08:00
using Newtonsoft.Json ;
2017-12-21 18:40:41 +08:00
using osu.Framework.Lists ;
2017-05-23 12:55:18 +08:00
namespace osu.Game.Beatmaps.ControlPoints
{
2017-12-06 15:23:51 +08:00
[Serializable]
2017-05-23 12:55:18 +08:00
public class ControlPointInfo
{
2017-05-23 14:29:38 +08:00
/// <summary>
/// All timing points.
/// </summary>
2017-12-06 15:23:51 +08:00
[JsonProperty]
2017-12-21 18:40:41 +08:00
public SortedList < TimingControlPoint > TimingPoints { get ; private set ; } = new SortedList < TimingControlPoint > ( Comparer < TimingControlPoint > . Default ) ;
2017-05-23 14:29:38 +08:00
/// <summary>
/// All difficulty points.
/// </summary>
2017-12-06 15:23:51 +08:00
[JsonProperty]
2017-12-21 18:40:41 +08:00
public SortedList < DifficultyControlPoint > DifficultyPoints { get ; private set ; } = new SortedList < DifficultyControlPoint > ( Comparer < DifficultyControlPoint > . Default ) ;
2017-05-23 14:29:38 +08:00
/// <summary>
/// All sound points.
/// </summary>
2017-12-06 15:23:51 +08:00
[JsonProperty]
2017-12-23 15:31:45 +08:00
public SortedList < SampleControlPoint > SamplePoints { get ; private set ; } = new SortedList < SampleControlPoint > ( Comparer < SampleControlPoint > . Default ) ;
2017-12-21 18:40:41 +08:00
2017-05-23 14:29:38 +08:00
/// <summary>
/// All effect points.
/// </summary>
2017-12-21 18:40:41 +08:00
[JsonProperty]
public SortedList < EffectControlPoint > EffectPoints { get ; private set ; } = new SortedList < EffectControlPoint > ( Comparer < EffectControlPoint > . Default ) ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the difficulty control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the difficulty control point at.</param>
/// <returns>The difficulty control point.</returns>
2017-05-23 14:20:32 +08:00
public DifficultyControlPoint DifficultyPointAt ( double time ) = > binarySearch ( DifficultyPoints , time ) ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the effect control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the effect control point at.</param>
/// <returns>The effect control point.</returns>
2017-05-23 14:20:32 +08:00
public EffectControlPoint EffectPointAt ( double time ) = > binarySearch ( EffectPoints , time ) ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the sound control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the sound control point at.</param>
/// <returns>The sound control point.</returns>
2017-12-23 15:46:02 +08:00
public SampleControlPoint SamplePointAt ( double time ) = > binarySearch ( SamplePoints , time , SamplePoints . FirstOrDefault ( ) ) ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the timing control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the timing control point at.</param>
/// <returns>The timing control point.</returns>
2017-05-24 01:53:08 +08:00
public TimingControlPoint TimingPointAt ( double time ) = > binarySearch ( TimingPoints , time , TimingPoints . FirstOrDefault ( ) ) ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the maximum BPM represented by any timing control point.
/// </summary>
2018-01-05 00:20:24 +08:00
[JsonIgnore]
2017-05-23 12:55:18 +08:00
public double BPMMaximum = >
2017-05-23 14:20:32 +08:00
60000 / ( TimingPoints . OrderBy ( c = > c . BeatLength ) . FirstOrDefault ( ) ? ? new TimingControlPoint ( ) ) . BeatLength ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the minimum BPM represented by any timing control point.
/// </summary>
2018-01-05 00:20:24 +08:00
[JsonIgnore]
2017-05-23 12:55:18 +08:00
public double BPMMinimum = >
2017-05-23 14:20:32 +08:00
60000 / ( TimingPoints . OrderByDescending ( c = > c . BeatLength ) . FirstOrDefault ( ) ? ? new TimingControlPoint ( ) ) . BeatLength ;
2017-05-23 12:55:18 +08:00
/// <summary>
/// Finds the mode BPM (most common BPM) represented by the control points.
/// </summary>
2018-01-05 00:20:24 +08:00
[JsonIgnore]
2017-05-23 12:55:18 +08:00
public double BPMMode = >
2017-05-23 14:20:32 +08:00
60000 / ( TimingPoints . GroupBy ( c = > c . BeatLength ) . OrderByDescending ( grp = > grp . Count ( ) ) . FirstOrDefault ( ) ? . FirstOrDefault ( ) ? ? new TimingControlPoint ( ) ) . BeatLength ;
2017-05-24 01:18:25 +08:00
/// <summary>
2017-05-24 01:22:28 +08:00
/// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
2017-05-24 01:18:25 +08:00
/// </summary>
/// <param name="list">The list to search.</param>
/// <param name="time">The time to find the control point at.</param>
2017-05-24 01:53:08 +08:00
/// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
2017-05-24 01:22:28 +08:00
/// <returns>The active control point at <paramref name="time"/>.</returns>
2017-12-21 18:40:41 +08:00
private T binarySearch < T > ( SortedList < T > list , double time , T prePoint = null )
2017-05-23 14:20:32 +08:00
where T : ControlPoint , new ( )
{
2017-11-08 06:08:24 +08:00
if ( list = = null )
throw new ArgumentNullException ( nameof ( list ) ) ;
2017-05-23 14:20:32 +08:00
if ( list . Count = = 0 )
return new T ( ) ;
if ( time < list [ 0 ] . Time )
2017-05-24 01:53:08 +08:00
return prePoint ? ? new T ( ) ;
2017-05-23 14:20:32 +08:00
2017-07-13 14:02:45 +08:00
int index = list . BinarySearch ( new T { Time = time } ) ;
2017-05-23 14:20:32 +08:00
// Check if we've found an exact match (t == time)
if ( index > = 0 )
return list [ index ] ;
index = ~ index ;
2017-05-24 01:24:10 +08:00
// 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)
2017-05-23 14:20:32 +08:00
return list [ index - 1 ] ;
}
2017-05-23 12:55:18 +08:00
}
2017-12-05 23:37:37 +08:00
}